diff --git a/api/http/handler/users/user_update.go b/api/http/handler/users/user_update.go index e61a62085..55e2f4e59 100644 --- a/api/http/handler/users/user_update.go +++ b/api/http/handler/users/user_update.go @@ -3,6 +3,7 @@ package users import ( "net/http" + "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" @@ -11,11 +12,16 @@ import ( ) type userUpdatePayload struct { + Username string Password string Role int } func (payload *userUpdatePayload) Validate(r *http.Request) error { + if govalidator.Contains(payload.Username, " ") { + return portainer.Error("Invalid username. Must not contain any whitespace") + } + if payload.Role != 0 && payload.Role != 1 && payload.Role != 2 { return portainer.Error("Invalid role value. Value must be one of: 1 (administrator) or 2 (regular user)") } @@ -55,6 +61,18 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a user with the specified identifier inside the database", err} } + if payload.Username != "" && payload.Username != user.Username { + sameNameUser, err := handler.DataStore.User().UserByUsername(payload.Username) + if err != nil && err != portainer.ErrObjectNotFound { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve users from the database", err} + } + if sameNameUser != nil && sameNameUser.ID != portainer.UserID(userID) { + return &httperror.HandlerError{http.StatusConflict, "Another user with the same username already exists", portainer.ErrUserAlreadyExists} + } + + user.Username = payload.Username + } + if payload.Password != "" { user.Password, err = handler.CryptoService.Hash(payload.Password) if err != nil { diff --git a/app/portainer/services/api/userService.js b/app/portainer/services/api/userService.js index d9950f772..ef23fffc3 100644 --- a/app/portainer/services/api/userService.js +++ b/app/portainer/services/api/userService.js @@ -78,12 +78,8 @@ angular.module('portainer.app').factory('UserService', [ return Users.remove({ id: id }).$promise; }; - service.updateUser = function (id, password, role) { - var query = { - password: password, - role: role, - }; - return Users.update({ id: id }, query).$promise; + service.updateUser = function (id, { password, role, username }) { + return Users.update({ id }, { password, role, username }).$promise; }; service.updateUserPassword = function (id, currentPassword, newPassword) { diff --git a/app/portainer/views/users/edit/user.html b/app/portainer/views/users/edit/user.html index 849d0d53c..bfc01dca4 100644 --- a/app/portainer/views/users/edit/user.html +++ b/app/portainer/views/users/edit/user.html @@ -1,7 +1,7 @@ - Users > {{ user.Username }} + Users > {{ formValues.username }} @@ -9,30 +9,35 @@
- - - - - - - - - - - -
- {{ user.Username }} - -
- - -
+ +
+
+ +
+ +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
diff --git a/app/portainer/views/users/edit/userController.js b/app/portainer/views/users/edit/userController.js index ec227fe44..93b3e6d9d 100644 --- a/app/portainer/views/users/edit/userController.js +++ b/app/portainer/views/users/edit/userController.js @@ -14,6 +14,7 @@ angular.module('portainer.app').controller('UserController', [ }; $scope.formValues = { + username: '', newPassword: '', confirmPassword: '', Administrator: false, @@ -28,12 +29,33 @@ angular.module('portainer.app').controller('UserController', [ }); }; - $scope.updatePermissions = function () { - var role = $scope.formValues.Administrator ? 1 : 2; - UserService.updateUser($scope.user.Id, undefined, role, 0) + $scope.updateUser = async function () { + const role = $scope.formValues.Administrator ? 1 : 2; + const oldUsername = $scope.user.Username; + const username = $scope.formValues.username; + let promise = Promise.resolve(true); + if (username != oldUsername) { + promise = new Promise((resolve) => + ModalService.confirm({ + title: 'Are you sure?', + message: `Are you sure you want to rename the user ${oldUsername} to ${username}?`, + buttons: { + confirm: { + label: 'Update', + className: 'btn-primary', + }, + }, + callback: resolve, + }) + ); + } + const confirmed = await promise; + if (!confirmed) { + return; + } + UserService.updateUser($scope.user.Id, { role, username }) .then(function success() { - var newRole = role === 1 ? 'administrator' : 'user'; - Notifications.success('Permissions successfully updated', $scope.user.Username + ' is now ' + newRole); + Notifications.success('User successfully updated'); $state.reload(); }) .catch(function error(err) { @@ -42,7 +64,7 @@ angular.module('portainer.app').controller('UserController', [ }; $scope.updatePassword = function () { - UserService.updateUser($scope.user.Id, $scope.formValues.newPassword, undefined, -1) + UserService.updateUser($scope.user.Id, { password: $scope.formValues.newPassword }) .then(function success() { Notifications.success('Password successfully updated'); $state.reload(); @@ -63,6 +85,12 @@ angular.module('portainer.app').controller('UserController', [ }); } + $scope.isSubmitEnabled = isSubmitEnabled; + function isSubmitEnabled() { + const { user, formValues } = $scope; + return user && (user.Username !== formValues.username || (formValues.Administrator && user.Role !== 1) || (!formValues.Administrator && user.Role === 1)); + } + function initView() { $scope.isAdmin = Authentication.isAdmin(); @@ -74,6 +102,7 @@ angular.module('portainer.app').controller('UserController', [ var user = data.user; $scope.user = user; $scope.formValues.Administrator = user.Role === 1; + $scope.formValues.username = user.Username; $scope.AuthenticationMethod = data.settings.AuthenticationMethod; }) .catch(function error(err) {