mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
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
This commit is contained in:
parent
0ea9fa19c4
commit
0df36cd8a6
9 changed files with 141 additions and 23 deletions
3
changelog/30055.txt
Normal file
3
changelog/30055.txt
Normal file
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -348,6 +348,13 @@
|
|||
@queryParam="role"
|
||||
@type={{attr.type}}
|
||||
/>
|
||||
{{else if (eq attr.name "skip_static_role_rotation_import")}}
|
||||
<InfoTableRow
|
||||
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
|
||||
@defaultShown={{defaultDisplay}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{not (get @model attr.name)}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@alwaysRender={{not (is-empty-value (get @model attr.name) hasDefault=defaultDisplay)}}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,14 @@
|
|||
@value={{format-duration (get @model attr.name)}}
|
||||
@isLink={{eq attr.name "database"}}
|
||||
/>
|
||||
{{else if (eq attr.name "skip_import_rotation")}}
|
||||
<InfoTableRow
|
||||
@alwaysRender={{true}}
|
||||
@defaultShown={{defaultDisplay}}
|
||||
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{not (get @model attr.name)}}
|
||||
@isLink={{eq attr.name "database"}}
|
||||
/>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@alwaysRender={{true}}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@
|
|||
<div class="form-section">
|
||||
{{#each this.settingFields as |attr|}}
|
||||
{{#if (and (eq @mode "edit") (includes attr.name (array "skip_import_rotation" "username")))}}
|
||||
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
|
||||
{{#if (eq attr.name "skip_import_rotation")}}
|
||||
<ReadonlyFormField @attr={{attr}} @value={{not (get @model attr.name)}} />
|
||||
{{else}}
|
||||
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{@modelValidations}} />
|
||||
{{#if (and (eq attr.name "skip_import_rotation") this.isOverridden)}}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@
|
|||
<Toggle
|
||||
@name="toggle-{{@attr.name}}"
|
||||
@onChange={{this.toggleButton}}
|
||||
@checked={{this.toggleInputEnabled}}
|
||||
@checked={{if @attr.options.isOppositeValue (not this.toggleInputEnabled) this.toggleInputEnabled}}
|
||||
data-test-toggle={{@attr.name}}
|
||||
disabled={{this.disabled}}
|
||||
>
|
||||
|
|
@ -288,9 +288,9 @@
|
|||
<div class="description has-text-grey" data-test-toggle-subtext>
|
||||
<span>
|
||||
{{#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}}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);
|
||||
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`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="create"/>`);
|
||||
await click('[data-test-secret-save]');
|
||||
|
||||
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue