UI: Add password field to static role creation page (#30275)

* adding password to static roles

* adding check for password rotation to disable password edit

* update field type and tests

* adding changelog

* replacing readonly with enableinput, added disable arg, test updates

* update to unless

* PR comments
This commit is contained in:
Dan Rivera 2025-04-21 16:10:58 -04:00 committed by GitHub
parent 766f97a9d4
commit ceb9c6d062
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 54 additions and 15 deletions

3
changelog/30275.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
ui/database: Adding password input field for creating a static role
```

View file

@ -125,6 +125,8 @@ export default class RoleModel extends Model {
})
revocation_statement;
@attr('string', { readOnly: true }) last_vault_rotation;
// ENTERPRISE ONLY
@attr({
label: 'Rotate immediately',
@ -135,6 +137,12 @@ export default class RoleModel extends Model {
})
skip_import_rotation;
@attr('string', {
sensitive: true,
subText: 'The database password that this Vault role corresponds to.',
})
password;
/* FIELD ATTRIBUTES */
get fieldAttrs() {
// Main fields on edit/create form
@ -156,6 +164,7 @@ export default class RoleModel extends Model {
'default_ttl',
'max_ttl',
'username',
'password',
'rotation_period',
'skip_import_rotation',
'creation_statements',
@ -169,7 +178,9 @@ export default class RoleModel extends Model {
// remove enterprise-only attrs if on community
if (!this.version.isEnterprise) {
allRoleSettingFields = allRoleSettingFields.filter((role) => role !== 'skip_import_rotation');
allRoleSettingFields = allRoleSettingFields.filter(
(role) => !['skip_import_rotation', 'password'].includes(role)
);
}
return expandAttributeMeta(this, allRoleSettingFields);

View file

@ -14,6 +14,16 @@
{{else}}
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
{{/if}}
{{else if (and (eq @mode "edit") (eq attr.name "password"))}}
<EnableInput
data-test-enable-field={{attr.name}}
class="field"
@attr={{attr}}
{{! password field is disabled on edit once password has been rotated }}
@disabled={{(not (eq (get @model "last_vault_rotation") undefined))}}
>
<FormField @attr={{attr}} @model={{@model}} />
</EnableInput>
{{else}}
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{@modelValidations}} />
{{#if (and (eq attr.name "skip_import_rotation") this.isOverridden)}}

View file

@ -197,7 +197,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
];
export const ROLE_FIELDS = {
static: ['username', 'rotation_period', 'skip_import_rotation'],
static: ['username', 'password', 'rotation_period', 'skip_import_rotation'],
dynamic: ['default_ttl', 'max_ttl'],
};

View file

@ -14,14 +14,16 @@
<Input aria-label={{@label}} readonly class="input" @type="text" @value="**********" />
{{/if}}
</div>
<div class="align-self-end">
<Hds::Button
@text="Enable input"
@icon="edit"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (fn (mut this.enable))}}
/>
</div>
{{#unless @disabled}}
<div class="align-self-end">
<Hds::Button
@text="Enable input"
@icon="edit"
@isIconOnly={{true}}
@color="tertiary"
{{on "click" (fn (mut this.enable))}}
/>
</div>
{{/unless}}
</div>
{{/if}}

View file

@ -9,6 +9,7 @@ import { tracked } from '@glimmer/tracking';
interface Args {
attr?: AttrData;
label?: string;
disabled?: boolean; // specifically used for disabling on edit
}
interface AttrData {
name: string; // required if @attr is passed
@ -38,6 +39,7 @@ interface AttrData {
* @param {object} [attr] - used to generate label for `ReadonlyFormField`, `name` key is required. Can be an attribute from a model exported with expandAttributeMeta.
* @param {string} [label] - required if no attr passed. Used to ensure a11y conformance for the readonly input.
* @param {boolean} [disabled=false] - to be used in specific scenarios where a user can visually see but not interact with the input field. ie. disabling a field on edit
*/
export default class EnableInputComponent extends Component<Args> {

View file

@ -10,6 +10,7 @@ import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { capabilitiesStub } from 'vault/tests/helpers/stubs';
import { click, fillIn } from '@ember/test-helpers';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | database-role-edit', function (hooks) {
setupRenderingTest(hooks);
@ -87,6 +88,7 @@ module('Integration | Component | database-role-edit', function (hooks) {
{
path: 'static-roles',
username: 'staticTestUser',
password: 'testPassword',
rotation_period: '172800s', // 2 days in seconds
skip_import_rotation: true,
},
@ -95,13 +97,18 @@ module('Integration | Component | database-role-edit', function (hooks) {
});
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="create"/>`);
await fillIn('[data-test-ttl-value="Rotation period"]', '2');
await click('[data-test-toggle-input="toggle-skip_import_rotation"]');
await fillIn(GENERAL.ttl.input('Rotation period'), '2');
await click(GENERAL.toggleInput('toggle-skip_import_rotation'));
await fillIn(GENERAL.inputByAttr('password'), 'testPassword'); // fill in password field
await click('[data-test-secret-save]');
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);
assert.dom('[data-test-value-div="Rotate immediately"]').containsText('No');
assert.dom(GENERAL.infoRowValue('Rotate immediately')).containsText('No');
assert.dom(GENERAL.infoRowValue('password')).doesNotExist(); // verify password field doesn't show on details view
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="edit"/>`);
assert.dom(GENERAL.icon('edit')).exists(); // verify password field is enabled for edit & enable button is rendered bc role hasn't been rotated
});
test('enterprise: it should successfully create user that does rotate immediately & verify warning modal pops up', async function (assert) {
@ -115,7 +122,11 @@ module('Integration | Component | database-role-edit', function (hooks) {
await click('[data-test-issuer-save]'); // click continue button on modal
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);
assert.dom('[data-test-value-div="Rotate immediately"]').containsText('Yes');
assert.dom(GENERAL.infoRowValue('Rotate immediately')).containsText('Yes');
this.modelStatic.last_vault_rotation = '2025-04-21T12:51:59.063124-04:00'; // Setting a sample rotation time here to simulate what returns from BE after rotation
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="edit"/>`);
assert.dom(GENERAL.icon('edit')).doesNotExist(); // verify password field is disabled for edit & enable button isn't rendered bc role has already been rotated
});
test('it should show Get credentials button when a user has the correct policy', async function (assert) {