From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH manager 6/9] ui: dc options: allow editing the API token policy
Date: Wed, 23 Sep 2026 22:59:55 +0200 [thread overview]
Message-ID: <20260923210000.4031318-7-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20260923210000.4031318-1-t.lamprecht@proxmox.com>
Render and edit the new datacenter token-policy option. The maximum
lifetime is entered in days, fractional values are allowed for
sub-day lifetimes, and the exact stored value is kept when the field
is left untouched, so a finer grained value set via the API survives
unrelated edits.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
www/manager6/dc/OptionView.js | 107 ++++++++++++++++++++++++++++++++++
1 file changed, 107 insertions(+)
diff --git a/www/manager6/dc/OptionView.js b/www/manager6/dc/OptionView.js
index dc12aa7e1..595a8f070 100644
--- a/www/manager6/dc/OptionView.js
+++ b/www/manager6/dc/OptionView.js
@@ -384,6 +384,113 @@ Ext.define('PVE.dc.OptionView', {
},
],
});
+ me.rows['token-policy'] = {
+ required: true,
+ header: gettext('API Token Policy'),
+ renderer: function (policy) {
+ if (!policy) {
+ return Proxmox.Utils.NoneText;
+ }
+ let parts = [];
+ if (Number(policy['require-expiry'])) {
+ parts.push(gettext('Require Expiration Date'));
+ }
+ if (policy['max-lifetime'] !== undefined) {
+ let lifetime = Proxmox.Utils.format_duration_human(
+ Number(policy['max-lifetime']),
+ );
+ parts.push(Ext.String.format(gettext('Max. Lifetime: {0}'), lifetime));
+ }
+ if (Number(policy['disallow-expiry-changes'])) {
+ parts.push(gettext('Disallow Expiration Changes'));
+ }
+ if (Number(policy['require-privilege-separation'])) {
+ parts.push(gettext('Require Privilege Separation'));
+ }
+ return parts.length ? parts.join(', ') : Proxmox.Utils.NoneText;
+ },
+ editor: {
+ xtype: 'proxmoxWindowEdit',
+ subject: gettext('API Token Policy'),
+ onlineHelp: 'pveum_token_policy',
+ url: '/api2/extjs/cluster/options',
+ fieldDefaults: {
+ labelWidth: 150,
+ },
+ setValues: function (values) {
+ let me = this;
+ // work on a copy, max-lifetime is stored in seconds but entered in days,
+ // fractional values are allowed; round up at two decimals for display and
+ // remember the exact stored value so that saving with the field untouched
+ // does not alter it
+ let policy = Ext.apply({}, values['token-policy'] || {});
+ me.storedMaxLifetime = policy['max-lifetime'];
+ if (policy['max-lifetime'] !== undefined) {
+ policy['max-lifetime'] =
+ Math.ceil((Number(policy['max-lifetime']) / 86400) * 100) / 100;
+ }
+ Ext.Array.each(me.query('inputpanel'), (panel) => panel.setValues(policy));
+ },
+ items: [
+ {
+ xtype: 'inputpanel',
+ onGetValues: function (values) {
+ let me = this;
+ let win = me.up('proxmoxWindowEdit');
+ let policy = {};
+ if (values['require-expiry']) {
+ policy['require-expiry'] = 1;
+ }
+ if (values['disallow-expiry-changes']) {
+ policy['disallow-expiry-changes'] = 1;
+ }
+ if (values['require-privilege-separation']) {
+ policy['require-privilege-separation'] = 1;
+ }
+ let days = values['max-lifetime'];
+ if (days !== undefined && days !== null && days !== '') {
+ let untouched = !me.down('field[name=max-lifetime]').isDirty();
+ policy['max-lifetime'] =
+ untouched && win.storedMaxLifetime !== undefined
+ ? win.storedMaxLifetime
+ : Math.round(Number(days) * 86400);
+ }
+ if (Object.keys(policy).length === 0) {
+ return { delete: 'token-policy' };
+ }
+ return { 'token-policy': PVE.Parser.printPropertyString(policy) };
+ },
+ items: [
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'require-expiry',
+ uncheckedValue: 0,
+ fieldLabel: gettext('Require Expiration Date'),
+ },
+ {
+ xtype: 'numberfield',
+ name: 'max-lifetime',
+ minValue: 0.01,
+ emptyText: gettext('No limit'),
+ fieldLabel: gettext('Maximum Lifetime (days)'),
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'disallow-expiry-changes',
+ uncheckedValue: 0,
+ fieldLabel: gettext('Disallow Expiration Changes'),
+ },
+ {
+ xtype: 'proxmoxcheckbox',
+ name: 'require-privilege-separation',
+ uncheckedValue: 0,
+ fieldLabel: gettext('Require Privilege Separation'),
+ },
+ ],
+ },
+ ],
+ },
+ };
me.rows['tag-style'] = {
required: true,
renderer: (value) => {
--
2.47.3
next prev parent reply other threads:[~2026-09-23 21:00 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 20:59 [PATCH cluster/access-control/manager/docs/proxmox 0/9] fix #7805: add a datacenter-wide API token policy Thomas Lamprecht
2026-09-23 20:59 ` [PATCH cluster 1/9] datacenter config: add token-policy option Thomas Lamprecht
2026-09-23 20:59 ` [PATCH access-control 2/9] fix #7805: api: token: enforce datacenter token policy Thomas Lamprecht
2026-09-23 20:59 ` [PATCH docs 3/9] user management: document the API " Thomas Lamprecht
2026-09-23 20:59 ` [PATCH manager 4/9] ui: token edit: only submit the expiration date when changed Thomas Lamprecht
2026-09-23 20:59 ` [PATCH manager 5/9] api: cluster options: return token-policy without Sys.Audit Thomas Lamprecht
2026-09-23 20:59 ` Thomas Lamprecht [this message]
2026-09-23 20:59 ` [PATCH manager 7/9] ui: token edit: adapt to the datacenter API token policy Thomas Lamprecht
2026-09-23 20:59 ` [PATCH proxmox 8/9] access-control: add API token policy type with expiry checks Thomas Lamprecht
2026-09-23 20:59 ` [PATCH proxmox 9/9] access-control: enforce token policy on token create and update Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260923210000.4031318-7-t.lamprecht@proxmox.com \
--to=t.lamprecht@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.