mirror of https://github.com/portainer/portainer
feat(users): add the ability to rename a user (#3884)
* feat(users): update username in server * feat(users): add username text field * fix(users): rename label and change buttons size * feat(users): change update message * feat(users): disable submit when not changed * feat(users): confirm updating username * feat(users): minor UI update Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>pull/2776/merge
parent
7325407f5f
commit
25ca036070
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<rd-header>
|
||||
<rd-header-title title-text="User details"></rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="portainer.users">Users</a> > <a ui-sref="portainer.users.user({id: user.Id})">{{ user.Username }}</a>
|
||||
<a ui-sref="portainer.users">Users</a> > <a ui-sref="portainer.users.user({id: user.Id})">{{ formValues.username }}</a>
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
|
@ -9,30 +9,35 @@
|
|||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-user" title-text="User details"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label>Name</label></td>
|
||||
<td>
|
||||
{{ user.Username }}
|
||||
<button class="btn btn-xs btn-danger" ng-click="deleteUser()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this user</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="isAdmin">
|
||||
<td colspan="2">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Administrator
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="Administrators have access to Portainer settings management as well as full control over all defined endpoints and their resources."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.Administrator" ng-change="updatePermissions()" /><i></i> </label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal" style="margin-top: 15px;">
|
||||
<div class="form-group">
|
||||
<label for="username_field" class="col-sm-2 control-label text-left">Username</label>
|
||||
<div class="col-sm-8">
|
||||
<input class="form-control" ng-model="formValues.username" id="username_field" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="isAdmin">
|
||||
<div class="col-sm-4">
|
||||
<label for="permissions" class="control-label text-left">
|
||||
Administrator
|
||||
<portainer-tooltip
|
||||
position="bottom"
|
||||
message="Administrators have access to Portainer settings management as well as full control over all defined endpoints and their resources."
|
||||
></portainer-tooltip>
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.Administrator" /><i></i> </label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-primary btn-sm" ng-disabled="!isSubmitEnabled()" ng-click="updateUser()">Save</button>
|
||||
<button class="btn btn-danger btn-sm" ng-click="deleteUser()"><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this user</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue