From 0df36cd8a6cc76192416be6a184762d7687934c2 Mon Sep 17 00:00:00 2001 From: Dan Rivera Date: Mon, 31 Mar 2025 11:12:15 -0400 Subject: [PATCH] UI: Changing polarity for toggle logic with skip import rotation for roles (#30055) * changing polarity for skip import toggles * changelog & test fix * tests * adding to workflow test * rename * add opposite check and remove default * connection change * using beforeEach for static tests --- changelog/30055.txt | 3 + ui/app/models/database/connection.js | 7 +- ui/app/models/database/role.js | 45 ++++++++++--- .../components/database-connection.hbs | 7 ++ .../components/database-role-edit.hbs | 8 +++ .../components/database-role-setting-form.hbs | 6 +- ui/lib/core/addon/components/form-field.hbs | 6 +- .../secrets/backend/database/workflow-test.js | 65 +++++++++++++++++-- .../components/database-role-edit-test.js | 17 ++++- 9 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 changelog/30055.txt diff --git a/changelog/30055.txt b/changelog/30055.txt new file mode 100644 index 0000000000..4808afaaab --- /dev/null +++ b/changelog/30055.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui/database: Updating toggle buttons for skip_rotation_import to reverse polarity of values that get displayed versus whats sent to api +``` \ No newline at end of file diff --git a/ui/app/models/database/connection.js b/ui/app/models/database/connection.js index 39c418a7ef..c5ab3996ae 100644 --- a/ui/app/models/database/connection.js +++ b/ui/app/models/database/connection.js @@ -202,10 +202,11 @@ export default Model.extend({ // ENTERPRISE ONLY skip_static_role_rotation_import: attr({ editType: 'toggleButton', - label: 'Skip initial rotation on static roles', - helperTextDisabled: 'Vault automatically rotates static roles upon their initial creation.', - helperTextEnabled: 'Vault will not automatically rotate static role passwords upon creation.', + label: 'Rotate static roles immediately', + helperTextEnabled: 'Vault automatically rotates static roles upon their initial creation.', + helperTextDisabled: 'Vault will not automatically rotate static role passwords upon creation.', defaultValue: false, + isOppositeValue: true, }), self_managed: attr('boolean', { diff --git a/ui/app/models/database/role.js b/ui/app/models/database/role.js index f3447ccf01..dec14c8f4a 100644 --- a/ui/app/models/database/role.js +++ b/ui/app/models/database/role.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ import Model, { attr } from '@ember-data/model'; +import { service } from '@ember/service'; import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; import { getRoleFields } from 'vault/utils/model-helpers/database-helpers'; import { expandAttributeMeta } from 'vault/utils/field-to-attrs'; @@ -24,9 +25,14 @@ const validations = { }; @withModelValidations(validations) export default class RoleModel extends Model { + @service version; + idPrefix = 'role/'; + @attr('string', { readOnly: true }) backend; + @attr('string', { label: 'Role name' }) name; + @attr('array', { label: 'Connection name', editType: 'searchSelect', @@ -37,12 +43,14 @@ export default class RoleModel extends Model { subText: 'The database connection for which credentials will be generated.', }) database; + @attr('string', { label: 'Type of role', noDefault: true, possibleValues: ['static', 'dynamic'], }) type; + @attr({ editType: 'ttl', defaultValue: '1h', @@ -51,6 +59,7 @@ export default class RoleModel extends Model { defaultShown: 'Engine default', }) default_ttl; + @attr({ editType: 'ttl', defaultValue: '24h', @@ -59,7 +68,9 @@ export default class RoleModel extends Model { defaultShown: 'Engine default', }) max_ttl; + @attr('string', { subText: 'The database username that this Vault role corresponds to.' }) username; + @attr({ editType: 'ttl', defaultValue: '24h', @@ -68,38 +79,36 @@ export default class RoleModel extends Model { helperTextEnabled: 'Vault will rotate password after.', }) rotation_period; - @attr({ - label: 'Skip initial rotation', - editType: 'toggleButton', - defaultValue: false, // this defaultValue will be set in database-role-setting-form.js based on parent database value - helperTextDisabled: 'Vault will rotate password for this static role on creation.', - helperTextEnabled: "Vault will not rotate this role's password on creation.", - }) - skip_import_rotation; + @attr('array', { editType: 'stringArray', }) creation_statements; + @attr('array', { editType: 'stringArray', defaultShown: 'Default', }) revocation_statements; + @attr('array', { editType: 'stringArray', defaultShown: 'Default', }) rotation_statements; + @attr('array', { editType: 'stringArray', defaultShown: 'Default', }) rollback_statements; + @attr('array', { editType: 'stringArray', defaultShown: 'Default', }) renew_statements; + @attr('string', { editType: 'json', allowReset: true, @@ -107,6 +116,7 @@ export default class RoleModel extends Model { defaultShown: 'Default', }) creation_statement; + @attr('string', { editType: 'json', allowReset: true, @@ -114,6 +124,17 @@ export default class RoleModel extends Model { defaultShown: 'Default', }) revocation_statement; + + // ENTERPRISE ONLY + @attr({ + label: 'Rotate immediately', + editType: 'toggleButton', + helperTextEnabled: 'Vault will rotate the password for this static role on creation.', + helperTextDisabled: "Vault will not rotate this role's password on creation.", + isOppositeValue: true, + }) + skip_import_rotation; + /* FIELD ATTRIBUTES */ get fieldAttrs() { // Main fields on edit/create form @@ -131,7 +152,7 @@ export default class RoleModel extends Model { } get roleSettingAttrs() { // logic for which get displayed is on DatabaseRoleSettingForm - const allRoleSettingFields = [ + let allRoleSettingFields = [ 'default_ttl', 'max_ttl', 'username', @@ -145,6 +166,12 @@ export default class RoleModel extends Model { 'rollback_statements', 'renew_statements', ]; + + // remove enterprise-only attrs if on community + if (!this.version.isEnterprise) { + allRoleSettingFields = allRoleSettingFields.filter((role) => role !== 'skip_import_rotation'); + } + return expandAttributeMeta(this, allRoleSettingFields); } /* CAPABILITIES */ diff --git a/ui/app/templates/components/database-connection.hbs b/ui/app/templates/components/database-connection.hbs index bb474de3bc..69545a4415 100644 --- a/ui/app/templates/components/database-connection.hbs +++ b/ui/app/templates/components/database-connection.hbs @@ -348,6 +348,13 @@ @queryParam="role" @type={{attr.type}} /> + {{else if (eq attr.name "skip_static_role_rotation_import")}} + {{else}} + {{else if (eq attr.name "skip_import_rotation")}} + {{else}} {{#each this.settingFields as |attr|}} {{#if (and (eq @mode "edit") (includes attr.name (array "skip_import_rotation" "username")))}} - + {{#if (eq attr.name "skip_import_rotation")}} + + {{else}} + + {{/if}} {{else}} {{#if (and (eq attr.name "skip_import_rotation") this.isOverridden)}} diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index bcdeb47ade..c63a823455 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -280,7 +280,7 @@ @@ -288,9 +288,9 @@
{{#if this.toggleInputEnabled}} - {{@attr.options.helperTextEnabled}} + {{if @attr.options.isOppositeValue @attr.options.helperTextDisabled @attr.options.helperTextEnabled}} {{else}} - {{@attr.options.helperTextDisabled}} + {{if @attr.options.isOppositeValue @attr.options.helperTextEnabled @attr.options.helperTextDisabled}} {{/if}}
diff --git a/ui/tests/acceptance/secrets/backend/database/workflow-test.js b/ui/tests/acceptance/secrets/backend/database/workflow-test.js index fd70c62981..d8a604ca1e 100644 --- a/ui/tests/acceptance/secrets/backend/database/workflow-test.js +++ b/ui/tests/acceptance/secrets/backend/database/workflow-test.js @@ -88,7 +88,7 @@ module('Acceptance | database workflow', function (hooks) { label: 'Root rotation statements', value: `Default`, }, - { label: 'Skip initial rotation on static roles', value: 'No' }, + { label: 'Rotate static roles immediately', value: 'Yes' }, ]; }); test('create with rotate', async function (assert) { @@ -121,7 +121,7 @@ module('Acceptance | database workflow', function (hooks) { assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows'); this.expectedRows.forEach(({ label, value }) => { const valueSelector = - label === 'Skip initial rotation on static roles' + label === 'Rotate static roles immediately' ? PAGE.infoRowValueDiv(label) : PAGE.infoRowValue(label); assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`); @@ -158,7 +158,7 @@ module('Acceptance | database workflow', function (hooks) { assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows'); this.expectedRows.forEach(({ label, value }) => { const valueSelector = - label === 'Skip initial rotation on static roles' + label === 'Rotate static roles immediately' ? PAGE.infoRowValueDiv(label) : PAGE.infoRowValue(label); assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`); @@ -202,7 +202,7 @@ module('Acceptance | database workflow', function (hooks) { assert.dom(PAGE.infoRow).exists({ count: this.expectedRows.length }, 'correct number of rows'); this.expectedRows.forEach(({ label, value }) => { const valueSelector = - label === 'Skip initial rotation on static roles' + label === 'Rotate static roles immediately' ? PAGE.infoRowValueDiv(label) : PAGE.infoRowValue(label); assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`); @@ -238,7 +238,7 @@ module('Acceptance | database workflow', function (hooks) { ); }); }); - module('roles', function (hooks) { + module('dynamic roles', function (hooks) { hooks.beforeEach(async function () { this.connection = `connect-${this.backend}`; await visit(`/vault/secrets/${this.backend}/create`); @@ -356,4 +356,59 @@ module('Acceptance | database workflow', function (hooks) { .hasText(`database/creds/${roleName}/abcd`, 'shows lease ID from response'); }); }); + + module('static roles', function (hooks) { + hooks.beforeEach(async function () { + this.setup = async ({ toggleRotateOff = false }) => { + this.connection = `connect-${this.backend}`; + await visit(`/vault/secrets/${this.backend}/create`); + await fillOutConnection(this.connection); + if (toggleRotateOff) { + await click('[data-test-toggle-input="toggle-skip_static_role_rotation_import"]'); + } + await click(FORM.saveBtn); + await visit(`/vault/secrets/${this.backend}/show/${this.connection}`); + }; + }); + + test('set parent db to rotate static roles immediately, verify static role reflects that default', async function (assert) { + await this.setup({ toggleRotateOff: false }); + + const roleName = 'static-role'; + await click(PAGE.addRole); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.backend}/create?initialKey=${this.connection}&itemType=role`, + 'Takes you to create role page' + ); + + await fillIn(FORM.inputByAttr('name'), roleName); + + await fillIn(FORM.inputByAttr('type'), 'static'); + + assert + .dom('[data-test-toggle-subtext]') + .containsText(`Vault will rotate the password for this static role on creation.`); + }); + + test('set parent db to not rotate static roles immediately, verify static role reflects that default', async function (assert) { + await this.setup({ toggleRotateOff: true }); + + const roleName = 'static-role'; + await click(PAGE.addRole); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.backend}/create?initialKey=${this.connection}&itemType=role`, + 'Takes you to create role page' + ); + + await fillIn(FORM.inputByAttr('name'), roleName); + + await fillIn(FORM.inputByAttr('type'), 'static'); + + assert + .dom('[data-test-toggle-subtext]') + .containsText(`Vault will not rotate this role's password on creation.`); + }); + }); }); diff --git a/ui/tests/integration/components/database-role-edit-test.js b/ui/tests/integration/components/database-role-edit-test.js index 62f46ef84a..05e14f6091 100644 --- a/ui/tests/integration/components/database-role-edit-test.js +++ b/ui/tests/integration/components/database-role-edit-test.js @@ -16,6 +16,7 @@ module('Integration | Component | database-role-edit', function (hooks) { setupMirage(hooks); hooks.beforeEach(function () { + this.version = this.owner.lookup('service:version'); this.store = this.owner.lookup('service:store'); this.store.pushPayload('database-role', { modelName: 'database/role', @@ -76,7 +77,8 @@ module('Integration | Component | database-role-edit', function (hooks) { await click('[data-test-secret-save]'); }); - test('it should successfully create user with skip import rotation', async function (assert) { + test('enterprise: it should successfully create user that does not rotate immediately', async function (assert) { + this.version.type = 'enterprise'; this.server.post('/sys/capabilities-self', capabilitiesStub('database/static-creds/my-role', ['create'])); this.server.post(`/database/static-roles/my-static-role`, (schema, req) => { assert.true(true, 'request made to create static role'); @@ -99,7 +101,18 @@ module('Integration | Component | database-role-edit', function (hooks) { await click('[data-test-secret-save]'); await render(hbs``); - assert.dom('[data-test-value-div="Skip initial rotation"]').containsText('Yes'); + assert.dom('[data-test-value-div="Rotate immediately"]').containsText('No'); + }); + + test('enterprise: it should successfully create user that does rotate immediately', async function (assert) { + this.version.type = 'enterprise'; + this.server.post('/sys/capabilities-self', capabilitiesStub('database/static-creds/my-role', ['create'])); + + await render(hbs``); + await click('[data-test-secret-save]'); + + await render(hbs``); + assert.dom('[data-test-value-div="Rotate immediately"]').containsText('Yes'); }); test('it should show Get credentials button when a user has the correct policy', async function (assert) {