5
0
mirror of git://git.proxmox.com/git/proxmox-backup.git synced 2025-03-19 18:50:17 +03:00
proxmox-backup/www/config/UserView.js

266 lines
5.7 KiB
JavaScript
Raw Permalink Normal View History

Ext.define('pmx-users', {
extend: 'Ext.data.Model',
fields: [
'userid', 'firstname', 'lastname', 'email', 'comment', 'totp-locked',
{ type: 'boolean', name: 'enable', defaultValue: true },
{ type: 'date', dateFormat: 'timestamp', name: 'expire' },
],
idProperty: 'userid',
proxy: {
type: 'proxmox',
url: '/api2/json/access/users',
},
});
Ext.define('PBS.config.UserView', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pbsUserView',
stateful: true,
stateId: 'grid-users',
title: gettext('Users'),
controller: {
xclass: 'Ext.app.ViewController',
addUser: function() {
let me = this;
Ext.create('PBS.window.UserEdit', {
listeners: {
destroy: function() {
me.reload();
},
},
}).show();
},
editUser: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
if (selection.length < 1) return;
Ext.create('PBS.window.UserEdit', {
userid: selection[0].data.userid,
listeners: {
destroy: function() {
me.reload();
},
},
}).show();
},
setPassword: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
if (selection.length < 1) return;
Ext.create('Proxmox.window.PasswordEdit', {
userid: selection[0].data.userid,
add 'confirmation-password' parameter to user password change API/UI Similar to a recent change in pve-access-control [0], add a new 'confirmation-password' parameter to the change-password endpoint and require non-root users to confirm their passwords. Doing so avoids that an attacker that has direct access to a computer where a user is logged in to the PVE interface can change the password of said user and thus either prolong their possibility to attack, and/or create a denial of service situation, where the original user cannot login into the PVE host using their old credentials. Note that this might sound worse than it is, as for this attack to work the attacker needs either: - physical access to an unlocked computer that is currently logged in to a PVE host - having taken over such a computer already through some unrelated vulnerability As these required pre-conditions are pretty big implications, which allow (temporary) access to all of the resources (including PVE ones) that the user can control, we see this as slight improvement that won't hurt, might protect one in some specific cases that is simply too cheap not to do. For now we avoid additional confirmation through a second factor, as that is a much higher complexity without that much gain, and some forms like (unauthenticated) button press on a WebAuthn token or the TOTP code would be easy to circumvent in the physical access case and in the local access case one might be able to MITM themselves too. [0]: https://git.proxmox.com/?p=pve-access-control.git;a=commit;h=5bcf553e3a193a537d92498f4fee3c23e22d1741 Reported-by: Wouter Arts <security@wth-security.nl> Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> [ TL: Extend ocmmit message, squash in UI change ] Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-03-15 14:31:46 +01:00
confirmCurrentPassword: Proxmox.UserName !== 'root@pam',
minLength: 8,
}).show();
},
showPermissions: function() {
let me = this;
let view = me.getView();
let selection = view.getSelection();
if (selection.length < 1) return;
Ext.create('Proxmox.PermissionView', {
auth_id: selection[0].data.userid,
auth_id_name: 'auth-id',
}).show();
},
renderName: function(val, cell, rec) {
let name = [];
if (rec.data.firstname) {
name.push(rec.data.firstname);
}
if (rec.data.lastname) {
name.push(rec.data.lastname);
}
return name.join(' ');
},
renderUsername: function(userid) {
return Ext.String.htmlEncode(userid.match(/^(.+)@([^@]+)$/)[1]);
},
renderRealm: function(userid) {
return Ext.String.htmlEncode(userid.match(/^(.+)@([^@]+)$/)[2]);
},
reload: function() { this.getView().getStore().rstore.load(); },
init: function(view) {
Proxmox.Utils.monStoreErrors(view, view.getStore().rstore);
},
unlockTfa: function(btn, event, rec) {
let me = this;
let view = me.getView();
Ext.Msg.confirm(
Ext.String.format(gettext('Unlock TFA authentication for {0}'), rec.data.userid),
gettext("Locked 2nd factors can happen if the user's password was leaked. Are you sure you want to unlock the user?"),
function(btn_response) {
if (btn_response === 'yes') {
Proxmox.Utils.API2Request({
url: `/access/users/${rec.data.userid}/unlock-tfa`,
waitMsgTarget: view,
method: 'PUT',
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
me.reload();
},
});
}
},
);
},
},
listeners: {
activate: 'reload',
itemdblclick: 'editUser',
},
store: {
type: 'diff',
autoDestroy: true,
autoDestroyRstore: true,
sorters: 'userid',
rstore: {
type: 'update',
storeid: 'pmx-users',
model: 'pmx-users',
autoStart: true,
interval: 5000,
},
},
tbar: [
{
xtype: 'proxmoxButton',
text: gettext('Add'),
handler: 'addUser',
selModel: false,
},
'-',
{
xtype: 'proxmoxButton',
text: gettext('Edit'),
handler: 'editUser',
disabled: true,
},
{
xtype: 'proxmoxStdRemoveButton',
baseurl: '/access/users/',
enableFn: (rec) => rec.data.userid !== 'root@pam',
getUrl: (rec) =>
`/access/users/${encodeURIComponent(rec.getId())}`,
callback: 'reload',
},
'-',
{
xtype: 'proxmoxButton',
text: gettext('Change Password'),
handler: 'setPassword',
disabled: true,
},
{
xtype: 'proxmoxButton',
text: gettext('Show Permissions'),
handler: 'showPermissions',
disabled: true,
},
'-',
{
xtype: 'proxmoxButton',
text: gettext('Unlock TFA'),
handler: 'unlockTfa',
disabled: true,
enableFn: ({ data }) =>
data['totp-locked'] || (data['tfa-locked-until'] > (new Date().getTime() / 1000)),
},
],
viewConfig: {
trackOver: false,
},
columns: [
{
header: gettext('User name'),
width: 200,
sortable: true,
renderer: 'renderUsername',
dataIndex: 'userid',
},
{
header: gettext('Realm'),
width: 100,
sortable: true,
renderer: 'renderRealm',
dataIndex: 'userid',
},
{
header: gettext('Enabled'),
width: 80,
sortable: true,
renderer: Proxmox.Utils.format_boolean,
dataIndex: 'enable',
},
{
header: gettext('Expire'),
width: 80,
sortable: true,
renderer: Proxmox.Utils.format_expire,
dataIndex: 'expire',
},
{
header: gettext('Name'),
width: 150,
sortable: true,
dataIndex: 'firstname',
renderer: 'renderName',
},
{
header: gettext('TFA Lock'),
width: 120,
sortable: true,
dataIndex: 'totp-locked',
renderer: function(v, metaData, record) {
let locked_until = record.data['tfa-locked-until'];
if (locked_until !== undefined) {
let now = new Date().getTime() / 1000;
if (locked_until > now) {
return gettext('Locked');
}
}
if (record.data['totp-locked']) {
return gettext('TOTP Locked');
}
return Proxmox.Utils.noText;
},
},
{
header: gettext('Comment'),
sortable: false,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment',
flex: 1,
},
],
});