From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D58E77D8A7 for ; Tue, 9 Nov 2021 12:28:23 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5F7A1B08E for ; Tue, 9 Nov 2021 12:27:41 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id F3BB0B049 for ; Tue, 9 Nov 2021 12:27:29 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id BF0DA42669 for ; Tue, 9 Nov 2021 12:27:29 +0100 (CET) From: Wolfgang Bumiller To: pve-devel@lists.proxmox.com Date: Tue, 9 Nov 2021 12:27:14 +0100 Message-Id: <20211109112721.130935-26-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20211109112721.130935-1-w.bumiller@proxmox.com> References: <20211109112721.130935-1-w.bumiller@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.505 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [record.data] Subject: [pve-devel] [PATCH manager 7/7] www: redirect user TFA button to TFA view X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 09 Nov 2021 11:28:24 -0000 Signed-off-by: Wolfgang Bumiller --- www/manager6/Makefile | 1 - www/manager6/StateProvider.js | 1 + www/manager6/Workspace.js | 6 +- www/manager6/dc/TFAEdit.js | 545 ---------------------------------- www/manager6/dc/UserView.js | 27 -- 5 files changed, 3 insertions(+), 577 deletions(-) delete mode 100644 www/manager6/dc/TFAEdit.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 4011d4e5..584c1f2a 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -149,7 +149,6 @@ JSSRC= \ dc/Summary.js \ dc/Support.js \ dc/SyncWindow.js \ - dc/TFAEdit.js \ dc/Tasks.js \ dc/TokenEdit.js \ dc/TokenView.js \ diff --git a/www/manager6/StateProvider.js b/www/manager6/StateProvider.js index e835f402..fafbb112 100644 --- a/www/manager6/StateProvider.js +++ b/www/manager6/StateProvider.js @@ -47,6 +47,7 @@ Ext.define('PVE.StateProvider', { hprefix: 'v1', compDict: { + tfa: 54, sdn: 53, cloudinit: 52, replication: 51, diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js index 0e2a750b..37d772b8 100644 --- a/www/manager6/Workspace.js +++ b/www/manager6/Workspace.js @@ -386,10 +386,8 @@ Ext.define('PVE.StdWorkspace', { itemId: 'tfaitem', iconCls: 'fa fa-fw fa-lock', handler: function(btn, event, rec) { - var win = Ext.create('PVE.window.TFAEdit', { - userid: Proxmox.UserName, - }); - win.show(); + Ext.state.Manager.getProvider().set('dctab', { value: 'tfa' }, true); + me.selectById('root'); }, }, { diff --git a/www/manager6/dc/TFAEdit.js b/www/manager6/dc/TFAEdit.js deleted file mode 100644 index 57a73b39..00000000 --- a/www/manager6/dc/TFAEdit.js +++ /dev/null @@ -1,545 +0,0 @@ -/*global u2f,QRCode*/ -Ext.define('PVE.window.TFAEdit', { - extend: 'Ext.window.Window', - mixins: ['Proxmox.Mixin.CBind'], - - onlineHelp: 'pveum_tfa_auth', // fake to ensure this gets a link target - - modal: true, - resizable: false, - title: gettext('Two Factor Authentication'), - subject: 'TFA', - url: '/api2/extjs/access/tfa', - width: 512, - - layout: { - type: 'vbox', - align: 'stretch', - }, - - updateQrCode: function() { - var me = this; - var values = me.lookup('totp_form').getValues(); - var algorithm = values.algorithm; - if (!algorithm) { - algorithm = 'SHA1'; - } - - me.qrcode.makeCode( - 'otpauth://totp/' + - encodeURIComponent(values.issuer) + - ':' + - encodeURIComponent(me.userid) + - '?secret=' + values.secret + - '&period=' + values.step + - '&digits=' + values.digits + - '&algorithm=' + algorithm + - '&issuer=' + encodeURIComponent(values.issuer), - ); - - me.lookup('challenge').setVisible(true); - me.down('#qrbox').setVisible(true); - }, - - showError: function(error) { - Ext.Msg.alert( - gettext('Error'), - Proxmox.Utils.render_u2f_error(error), - ); - }, - - doU2FChallenge: function(res) { - let me = this; - - let challenge = res.result.data; - me.lookup('password').setDisabled(true); - let msg = Ext.Msg.show({ - title: 'U2F: ' + gettext('Setup'), - message: gettext('Please press the button on your U2F Device'), - buttons: [], - }); - Ext.Function.defer(function() { - u2f.register(challenge.appId, [challenge], [], function(response) { - msg.close(); - if (response.errorCode) { - me.showError(response.errorCode); - } else { - me.respondToU2FChallenge(response); - } - }); - }, 500, me); - }, - - respondToU2FChallenge: function(data) { - var me = this; - var params = { - userid: me.userid, - action: 'confirm', - response: JSON.stringify(data), - }; - if (Proxmox.UserName !== 'root@pam') { - params.password = me.lookup('password').value; - } - Proxmox.Utils.API2Request({ - url: '/api2/extjs/access/tfa', - params: params, - method: 'PUT', - success: function() { - me.close(); - Ext.Msg.show({ - title: gettext('Success'), - message: gettext('U2F Device successfully connected.'), - buttons: Ext.Msg.OK, - }); - }, - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - }); - }, - - viewModel: { - data: { - in_totp_tab: true, - tfa_required: false, - tfa_type: null, // dependencies of formulas should not be undefined - valid: false, - u2f_available: true, - secret: "", - }, - formulas: { - showTOTPVerifiction: function(get) { - return get('secret').length > 0 && get('canSetupTOTP'); - }, - canDeleteTFA: function(get) { - return get('tfa_type') !== null && !get('tfa_required'); - }, - canSetupTOTP: function(get) { - var tfa = get('tfa_type'); - return tfa === null || tfa === 'totp' || tfa === 1; - }, - canSetupU2F: function(get) { - var tfa = get('tfa_type'); - return get('u2f_available') && (tfa === null || tfa === 'u2f' || tfa === 1); - }, - secretEmpty: function(get) { - return get('secret').length === 0; - }, - selectedTab: function(get) { - return (get('tfa_type') || 'totp') + '-panel'; - }, - }, - }, - - afterLoading: function(realm_tfa_type, user_tfa_type) { - var me = this; - var viewmodel = me.getViewModel(); - if (user_tfa_type === 'oath') { - user_tfa_type = 'totp'; - viewmodel.set('secret', ''); - } - - // if the user has no tfa, generate a secret for him - if (!user_tfa_type) { - me.getController().randomizeSecret(); - } - - viewmodel.set('tfa_type', user_tfa_type || null); - if (!realm_tfa_type) { - // There's no TFA enforced by the realm, everything works. - viewmodel.set('u2f_available', true); - viewmodel.set('tfa_required', false); - } else if (realm_tfa_type === 'oath') { - // The realm explicitly requires TOTP - if (user_tfa_type !== 'totp' && user_tfa_type !== null) { - // user had a different tfa method, so - // we have to change back to the totp tab and - // generate a secret - viewmodel.set('tfa_type', 'totp'); - me.getController().randomizeSecret(); - } - viewmodel.set('tfa_required', true); - viewmodel.set('u2f_available', false); - } else { - // The realm enforces some other TFA type (yubico) - me.close(); - Ext.Msg.alert( - gettext('Error'), - Ext.String.format( - gettext("Custom 2nd factor configuration is not supported on realms with '{0}' TFA."), - realm_tfa_type, - ), - ); - } - }, - - controller: { - xclass: 'Ext.app.ViewController', - control: { - 'field[qrupdate=true]': { - change: function() { - this.getView().updateQrCode(); - }, - }, - 'field': { - validitychange: function(field, valid) { - var me = this; - var viewModel = me.getViewModel(); - var form = me.lookup('totp_form'); - var challenge = me.lookup('challenge'); - var password = me.lookup('password'); - viewModel.set('valid', form.isValid() && challenge.isValid() && password.isValid()); - }, - }, - '#': { - show: function() { - let view = this.getView(); - - Proxmox.Utils.API2Request({ - url: '/access/users/' + encodeURIComponent(view.userid) + '/tfa', - waitMsgTarget: view.down('#tfatabs'), - method: 'GET', - success: function(response, opts) { - let data = response.result.data; - view.afterLoading(data.realm, data.user); - }, - failure: function(response, opts) { - view.close(); - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - }); - - view.qrdiv = document.createElement('center'); - view.qrcode = new QRCode(view.qrdiv, { - width: 256, - height: 256, - correctLevel: QRCode.CorrectLevel.M, - }); - view.down('#qrbox').getEl().appendChild(view.qrdiv); - - if (Proxmox.UserName === 'root@pam') { - view.lookup('password').setVisible(false); - view.lookup('password').setDisabled(true); - } - }, - }, - '#tfatabs': { - tabchange: function(panel, newcard) { - this.getViewModel().set('in_totp_tab', newcard.itemId === 'totp-panel'); - }, - }, - }, - - applySettings: function() { - let me = this; - let values = me.lookup('totp_form').getValues(); - let params = { - userid: me.getView().userid, - action: 'new', - key: 'v2-' + values.secret, - config: PVE.Parser.printPropertyString({ - type: 'oath', - digits: values.digits, - step: values.step, - }), - // this is used to verify that the client generates the correct codes: - response: me.lookup('challenge').value, - }; - - if (Proxmox.UserName !== 'root@pam') { - params.password = me.lookup('password').value; - } - - Proxmox.Utils.API2Request({ - url: '/api2/extjs/access/tfa', - params: params, - method: 'PUT', - waitMsgTarget: me.getView(), - success: function(response, opts) { - me.getView().close(); - }, - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - }); - }, - - deleteTFA: function() { - let me = this; - let params = { - userid: me.getView().userid, - action: 'delete', - }; - - if (Proxmox.UserName !== 'root@pam') { - params.password = me.lookup('password').value; - } - - Proxmox.Utils.API2Request({ - url: '/api2/extjs/access/tfa', - params: params, - method: 'PUT', - waitMsgTarget: me.getView(), - success: function(response, opts) { - me.getView().close(); - }, - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - }); - }, - - randomizeSecret: function() { - let me = this; - let rnd = new Uint8Array(32); - window.crypto.getRandomValues(rnd); - let data = ''; - rnd.forEach(function(b) { - // secret must be base32, so just use the first 5 bits - b = b & 0x1f; - if (b < 26) { - data += String.fromCharCode(b + 0x41); // A..Z - } else { - data += String.fromCharCode(b-26 + 0x32); // 2..7 - } - }); - me.getViewModel().set('secret', data); - }, - - startU2FRegistration: function() { - let me = this; - - let params = { - userid: me.getView().userid, - action: 'new', - }; - - if (Proxmox.UserName !== 'root@pam') { - params.password = me.lookup('password').value; - } - - Proxmox.Utils.API2Request({ - url: '/api2/extjs/access/tfa', - params: params, - method: 'PUT', - waitMsgTarget: me.getView(), - success: function(response) { - me.getView().doU2FChallenge(response); - }, - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - }); - }, - }, - - items: [ - { - xtype: 'tabpanel', - itemId: 'tfatabs', - reference: 'tfatabs', - border: false, - bind: { - activeTab: '{selectedTab}', - }, - items: [ - { - xtype: 'panel', - title: 'TOTP', - itemId: 'totp-panel', - reference: 'totp_panel', - tfa_type: 'totp', - border: false, - bind: { - disabled: '{!canSetupTOTP}', - }, - layout: { - type: 'vbox', - align: 'stretch', - }, - items: [ - { - xtype: 'form', - layout: 'anchor', - border: false, - reference: 'totp_form', - fieldDefaults: { - anchor: '100%', - padding: '0 5', - }, - items: [ - { - xtype: 'displayfield', - fieldLabel: gettext('User name'), - renderer: Ext.String.htmlEncode, - cbind: { - value: '{userid}', - }, - }, - { - layout: 'hbox', - border: false, - padding: '0 0 5 0', - items: [{ - xtype: 'textfield', - fieldLabel: gettext('Secret'), - emptyText: gettext('Unchanged'), - name: 'secret', - reference: 'tfa_secret', - regex: /^[A-Z2-7=]+$/, - regexText: 'Must be base32 [A-Z2-7=]', - maskRe: /[A-Z2-7=]/, - qrupdate: true, - bind: { - value: "{secret}", - }, - flex: 4, - }, - { - xtype: 'button', - text: gettext('Randomize'), - reference: 'randomize_button', - handler: 'randomizeSecret', - flex: 1, - }], - }, - { - xtype: 'numberfield', - fieldLabel: gettext('Time period'), - name: 'step', - // Google Authenticator ignores this and generates bogus data - hidden: true, - value: 30, - minValue: 10, - qrupdate: true, - }, - { - xtype: 'numberfield', - fieldLabel: gettext('Digits'), - name: 'digits', - value: 6, - // Google Authenticator ignores this and generates bogus data - hidden: true, - minValue: 6, - maxValue: 8, - qrupdate: true, - }, - { - xtype: 'textfield', - fieldLabel: gettext('Issuer Name'), - name: 'issuer', - value: 'Proxmox Web UI', - qrupdate: true, - }, - ], - }, - { - xtype: 'box', - itemId: 'qrbox', - visible: false, // will be enabled when generating a qr code - bind: { - visible: '{!secretEmpty}', - }, - style: { - 'background-color': 'white', - padding: '5px', - width: '266px', - height: '266px', - }, - }, - { - xtype: 'textfield', - fieldLabel: gettext('Verification Code'), - allowBlank: false, - reference: 'challenge', - bind: { - disabled: '{!showTOTPVerifiction}', - visible: '{showTOTPVerifiction}', - }, - padding: '0 5', - emptyText: gettext('Scan QR code and enter TOTP auth. code to verify'), - }, - ], - }, - { - title: 'U2F', - itemId: 'u2f-panel', - reference: 'u2f_panel', - tfa_type: 'u2f', - border: false, - padding: '5 5', - layout: { - type: 'vbox', - align: 'middle', - }, - bind: { - disabled: '{!canSetupU2F}', - }, - items: [ - { - xtype: 'label', - width: 500, - text: gettext('To register a U2F device, connect the device, then click the button and follow the instructions.'), - }, - ], - }, - ], - }, - { - xtype: 'textfield', - inputType: 'password', - fieldLabel: gettext('Password'), - minLength: 5, - reference: 'password', - allowBlank: false, - validateBlank: true, - padding: '0 0 5 5', - emptyText: gettext('verify current password'), - }, - ], - - buttons: [ - { - xtype: 'proxmoxHelpButton', - }, - '->', - { - text: gettext('Apply'), - handler: 'applySettings', - bind: { - hidden: '{!in_totp_tab}', - disabled: '{!valid}', - }, - }, - { - xtype: 'button', - text: gettext('Register U2F Device'), - handler: 'startU2FRegistration', - bind: { - hidden: '{in_totp_tab}', - disabled: '{tfa_type}', - }, - }, - { - text: gettext('Delete'), - reference: 'delete_button', - disabled: true, - handler: 'deleteTFA', - bind: { - disabled: '{!canDeleteTFA}', - }, - }, - ], - - initComponent: function() { - var me = this; - - if (!me.userid) { - throw "no userid given"; - } - - me.callParent(); - - Ext.GlobalEvents.fireEvent('proxmoxShowHelp', 'pveum_tfa_auth'); - }, -}); diff --git a/www/manager6/dc/UserView.js b/www/manager6/dc/UserView.js index 9c84bf7d..f397731d 100644 --- a/www/manager6/dc/UserView.js +++ b/www/manager6/dc/UserView.js @@ -77,32 +77,6 @@ Ext.define('PVE.dc.UserView', { }); }, }); - let tfachange_btn = new Proxmox.button.Button({ - text: 'TFA', - disabled: true, - selModel: sm, - enableFn: function(record) { - let type = record.data['realm-type']; - if (type) { - if (PVE.Utils.authSchema[type]) { - return !!PVE.Utils.authSchema[type].tfa; - } - } - return false; - }, - handler: function(btn, event, rec) { - var d = rec.data; - var tfa_type = PVE.Parser.parseTfaType(d.keys); - Ext.create('PVE.window.TFAEdit', { - tfa_type: tfa_type, - userid: d.userid, - autoShow: true, - listeners: { - destroy: () => reload(), - }, - }); - }, - }); var perm_btn = new Proxmox.button.Button({ text: gettext('Permissions'), @@ -140,7 +114,6 @@ Ext.define('PVE.dc.UserView', { remove_btn, '-', pwchange_btn, - tfachange_btn, '-', perm_btn, ], -- 2.30.2