mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
UI: Add Skip password rotation field on DB connection creation (#29820)
* adding skip flag to db creation * update field name & add default val change to static role * transfer both fields to be toggle buttons * add changelog * test updates * leftover * test fixes * fix tests pt2 * test pt3 * adding conditional to disable role type selection * adding alert when overriding db default * cleanup * pr comments pt1 - updates to logic, adding empty state & form field test * moving empty state placement * updating form field logic for subtext, test fixes * restructuring a bit to use a getter / eliminate separate function * update * fix typo, bring back tests * fixes and cleanup
This commit is contained in:
parent
360e2f9d83
commit
404356a805
17 changed files with 187 additions and 63 deletions
3
changelog/29820.txt
Normal file
3
changelog/29820.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui/database: Adding input field for setting skip static role password rotation for database connection config, updating static role skip field to use toggle button
|
||||
```
|
||||
|
|
@ -46,7 +46,7 @@ export default class DatabaseRoleEdit extends Component {
|
|||
isValid() {
|
||||
const { isValid, state } = this.args.model.validate();
|
||||
this.modelValidations = isValid ? null : state;
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
this.invalidFormAlert = isValid ? '' : 'There was an error submitting this form.';
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,8 @@ export default class DatabaseRoleEdit extends Component {
|
|||
}
|
||||
return warnings;
|
||||
}
|
||||
get databaseType() {
|
||||
|
||||
get databaseParams() {
|
||||
const backend = this.args.model?.backend;
|
||||
const dbs = this.args.model?.database || [];
|
||||
if (!backend || dbs.length === 0) {
|
||||
|
|
@ -75,7 +76,10 @@ export default class DatabaseRoleEdit extends Component {
|
|||
}
|
||||
return this.store
|
||||
.queryRecord('database/connection', { id: dbs[0], backend })
|
||||
.then((record) => record.plugin_name)
|
||||
.then(({ plugin_name, skip_static_role_rotation_import }) => ({
|
||||
plugin_name,
|
||||
skip_static_role_rotation_import,
|
||||
}))
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +114,7 @@ export default class DatabaseRoleEdit extends Component {
|
|||
this.resetErrors();
|
||||
const { mode, model } = this.args;
|
||||
if (!this.isValid()) return;
|
||||
|
||||
if (mode === 'create') {
|
||||
model.id = model.name;
|
||||
const path = model.type === 'static' ? 'static-roles' : 'roles';
|
||||
|
|
|
|||
|
|
@ -18,25 +18,41 @@ import { getStatementFields, getRoleFields } from '../utils/model-helpers/databa
|
|||
* @param {object} model - ember data model which should be updated on change
|
||||
* @param {string} [roleType] - role type controls which attributes are shown
|
||||
* @param {string} [mode=create] - mode of the form (eg. create or edit)
|
||||
* @param {string} [dbType=default] - type of database, eg 'mongodb-database-plugin'
|
||||
* @param {object} dbParams - holds database config values, { plugin_name: string [eg 'mongodb-database-plugin'], skip_static_role_rotation_import: boolean }
|
||||
*/
|
||||
|
||||
export default class DatabaseRoleSettingForm extends Component {
|
||||
get dbConfig() {
|
||||
return this.args.dbParams;
|
||||
}
|
||||
|
||||
get settingFields() {
|
||||
const dbValues = this.args.dbParams;
|
||||
if (!this.args.roleType) return null;
|
||||
const dbValidFields = getRoleFields(this.args.roleType);
|
||||
return this.args.attrs.filter((a) => {
|
||||
// Sets default value for skip_import_rotation based on parent db config value
|
||||
if (a.name === 'skip_import_rotation' && this.args.mode === 'create') {
|
||||
a.options.defaultValue = dbValues?.skip_static_role_rotation_import;
|
||||
}
|
||||
return dbValidFields.includes(a.name);
|
||||
});
|
||||
}
|
||||
|
||||
get statementFields() {
|
||||
const type = this.args.roleType;
|
||||
const plugin = this.args.dbType;
|
||||
if (!type) return null;
|
||||
const dbValidFields = getStatementFields(type, plugin);
|
||||
const dbValidFields = getStatementFields(type, this.dbConfig ? this.dbConfig.plugin_name : null);
|
||||
return this.args.attrs.filter((a) => {
|
||||
return dbValidFields.includes(a.name);
|
||||
});
|
||||
}
|
||||
|
||||
get isOverridden() {
|
||||
if (this.args.mode !== 'create' || !this.dbConfig) return null;
|
||||
|
||||
const dbSkip = this.dbConfig.skip_static_role_rotation_import;
|
||||
const staticVal = this.args.model.get('skip_import_rotation');
|
||||
return this.args.mode === 'create' && dbSkip !== staticVal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,6 +200,14 @@ 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.',
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
self_managed: attr('boolean', {
|
||||
subText:
|
||||
'Allows onboarding static roles with a rootless connection configuration. Mutually exclusive with username and password. If true, will force verify_connection to be false.',
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { getRoleFields } from 'vault/utils/model-helpers/database-helpers';
|
|||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
const validations = {
|
||||
name: [{ type: 'presence', message: 'Role name is required.' }],
|
||||
database: [{ type: 'presence', message: 'Database is required.' }],
|
||||
type: [{ type: 'presence', message: 'Type is required.' }],
|
||||
username: [
|
||||
|
|
@ -69,9 +70,10 @@ export default class RoleModel extends Model {
|
|||
rotation_period;
|
||||
@attr({
|
||||
label: 'Skip initial rotation',
|
||||
editType: 'boolean',
|
||||
defaultValue: false,
|
||||
subText: 'When unchecked, Vault automatically rotates the password upon creation.',
|
||||
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', {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,23 @@
|
|||
{{#if (eq @mode "edit")}}
|
||||
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
|
||||
{{else if (not-eq attr.options.readOnly true)}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{this.modelValidations}} />
|
||||
{{#if (eq attr.name "type")}}
|
||||
{{#if @model.database}}
|
||||
<FormField
|
||||
data-test-field={{true}}
|
||||
@attr={{attr}}
|
||||
@model={{@model}}
|
||||
@modelValidations={{this.modelValidations}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<FormField
|
||||
data-test-field={{true}}
|
||||
@attr={{attr}}
|
||||
@model={{@model}}
|
||||
@modelValidations={{this.modelValidations}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{! TODO: If database && !updateDB show warning }}
|
||||
{{#if (get this.warningMessages attr.name)}}
|
||||
<Hds::Alert @type="inline" @color="warning" class="has-top-margin-negative-s has-bottom-margin-s" as |A|>
|
||||
|
|
@ -116,14 +132,21 @@
|
|||
{{/if}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<DatabaseRoleSettingForm
|
||||
@attrs={{@model.roleSettingAttrs}}
|
||||
@roleType={{@model.type}}
|
||||
@model={{@model}}
|
||||
@mode={{@mode}}
|
||||
@dbType={{await this.databaseType}}
|
||||
@modelValidations={{this.modelValidations}}
|
||||
/>
|
||||
{{#if @model.database}}
|
||||
<DatabaseRoleSettingForm
|
||||
@attrs={{@model.roleSettingAttrs}}
|
||||
@roleType={{@model.type}}
|
||||
@model={{@model}}
|
||||
@mode={{@mode}}
|
||||
@dbParams={{await this.databaseParams}}
|
||||
@modelValidations={{this.modelValidations}}
|
||||
/>
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="No database connection selected"
|
||||
@message="Choose a connection to be able to configure a role type."
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="field is-fullwidth box is-bottomless">
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button
|
||||
|
|
|
|||
|
|
@ -8,10 +8,16 @@
|
|||
{{#if this.settingFields}}
|
||||
<div class="form-section">
|
||||
{{#each this.settingFields as |attr|}}
|
||||
{{#if (and (eq @mode "edit") (eq attr.name "username"))}}
|
||||
{{#if (and (eq @mode "edit") (includes attr.name (array "skip_import_rotation" "username")))}}
|
||||
<ReadonlyFormField @attr={{attr}} @value={{get @model attr.name}} />
|
||||
{{else}}
|
||||
<FormField data-test-field={{true}} @attr={{attr}} @model={{@model}} @modelValidations={{@modelValidations}} />
|
||||
{{#if (and (eq attr.name "skip_import_rotation") this.isOverridden)}}
|
||||
<Hds::Alert @type="inline" @color="warning" class="has-top-margin-negative-s" as |A|>
|
||||
<A.Title>Warning</A.Title>
|
||||
<A.Description>This will override the connection default for this role.</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'tls_server_name', group: 'pluginConfig' },
|
||||
{ attr: 'insecure', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -37,6 +38,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'password', group: 'pluginConfig', show: false },
|
||||
{ attr: 'write_concern', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
|
|
@ -57,6 +59,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_open_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
],
|
||||
},
|
||||
|
|
@ -75,6 +78,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
|
|
@ -95,6 +99,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
|
|
@ -115,6 +120,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
|
|
@ -135,6 +141,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'tls', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
|
|
@ -155,6 +162,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'max_idle_connections', group: 'pluginConfig' },
|
||||
{ attr: 'max_connection_lifetime', group: 'pluginConfig' },
|
||||
{ attr: 'username_template', group: 'pluginConfig' },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
],
|
||||
},
|
||||
|
|
@ -180,6 +188,7 @@ export const AVAILABLE_PLUGIN_TYPES = [
|
|||
{ attr: 'disable_escaping', group: 'pluginConfig' },
|
||||
{ attr: 'root_rotation_statements', group: 'statements' },
|
||||
{ attr: 'self_managed', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'skip_static_role_rotation_import', group: 'pluginConfig', isEnterprise: true },
|
||||
{ attr: 'private_key', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_ca', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
{ attr: 'tls_certificate', group: 'pluginConfig', subgroup: 'TLS options' },
|
||||
|
|
|
|||
|
|
@ -187,17 +187,37 @@
|
|||
@value={{or (get @model this.valuePath) @attr.options.defaultValue}}
|
||||
@onChange={{this.onChangeWithEvent}}
|
||||
/>
|
||||
{{else if (eq @attr.options.editType "toggleButton")}}
|
||||
{{! Togglable Input }}
|
||||
<Toggle
|
||||
@name="toggle-{{@attr.name}}"
|
||||
@onChange={{this.toggleButton}}
|
||||
@checked={{this.toggleInputEnabled}}
|
||||
data-test-toggle={{@attr.name}}
|
||||
disabled={{this.disabled}}
|
||||
>
|
||||
<span class="has-text-weight-bold is-large">{{this.labelString}}</span><br />
|
||||
<div class="description has-text-grey" data-test-toggle-subtext>
|
||||
<span>
|
||||
{{#if this.toggleInputEnabled}}
|
||||
{{@attr.options.helperTextEnabled}}
|
||||
{{else}}
|
||||
{{@attr.options.helperTextDisabled}}
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</Toggle>
|
||||
{{else if (eq @attr.options.editType "optionalText")}}
|
||||
{{! Togglable Text Input }}
|
||||
<Toggle
|
||||
@name="show-{{@attr.name}}"
|
||||
@onChange={{this.toggleShow}}
|
||||
@checked={{this.showInput}}
|
||||
@onChange={{this.toggleTextShow}}
|
||||
@checked={{this.showToggleTextInput}}
|
||||
data-test-toggle={{@attr.name}}
|
||||
>
|
||||
<span class="ttl-picker-label is-large">{{this.labelString}}</span><br />
|
||||
<div class="description has-text-grey">
|
||||
{{#if this.showInput}}
|
||||
{{#if this.showToggleTextInput}}
|
||||
<span>
|
||||
{{@attr.options.subText}}
|
||||
{{#if @attr.options.docLink}}
|
||||
|
|
@ -219,7 +239,7 @@
|
|||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if this.showInput}}
|
||||
{{#if this.showToggleTextInput}}
|
||||
<input
|
||||
data-test-input={{@attr.name}}
|
||||
id={{@attr.name}}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,10 @@ export default class FormFieldComponent extends Component {
|
|||
'searchSelect',
|
||||
'stringArray',
|
||||
'ttl',
|
||||
'toggleButton',
|
||||
];
|
||||
@tracked showInput = false;
|
||||
@tracked showToggleTextInput = false;
|
||||
@tracked toggleInputEnabled = false;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
|
@ -75,7 +77,8 @@ export default class FormFieldComponent extends Component {
|
|||
valuePath.toLowerCase() !== 'id'
|
||||
);
|
||||
const modelValue = model[valuePath];
|
||||
this.showInput = !!modelValue;
|
||||
this.showToggleTextInput = !!modelValue;
|
||||
this.toggleInputEnabled = !!modelValue;
|
||||
}
|
||||
|
||||
get hasRadioSubText() {
|
||||
|
|
@ -177,14 +180,19 @@ export default class FormFieldComponent extends Component {
|
|||
}
|
||||
}
|
||||
@action
|
||||
toggleShow() {
|
||||
const value = !this.showInput;
|
||||
this.showInput = value;
|
||||
toggleTextShow() {
|
||||
const value = !this.showToggleTextInput;
|
||||
this.showToggleTextInput = value;
|
||||
if (!value) {
|
||||
this.setAndBroadcast(null);
|
||||
}
|
||||
}
|
||||
@action
|
||||
toggleButton() {
|
||||
this.toggleInputEnabled = !this.toggleInputEnabled;
|
||||
this.setAndBroadcast(this.toggleInputEnabled);
|
||||
}
|
||||
@action
|
||||
handleKeyUp(maybeEvent) {
|
||||
const value = typeof maybeEvent === 'object' ? maybeEvent.target.value : maybeEvent;
|
||||
if (!this.args.onKeyUp) {
|
||||
|
|
|
|||
|
|
@ -574,13 +574,13 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
|||
await rolePage.name('bar');
|
||||
assert
|
||||
.dom('[data-test-component="empty-state"]')
|
||||
.exists({ count: 2 }, 'Two empty states exist before selections made');
|
||||
.exists({ count: 1 }, 'One empty state exists before database selection is made');
|
||||
await clickTrigger('#database');
|
||||
assert.strictEqual(searchSelectComponent.options.length, 1, 'list shows existing connections so far');
|
||||
await selectChoose('#database', '.ember-power-select-option', 0);
|
||||
assert
|
||||
.dom('[data-test-component="empty-state"]')
|
||||
.exists({ count: 2 }, 'Two empty states exist before selections made');
|
||||
.exists({ count: 2 }, 'Two empty states exist after a database is selected');
|
||||
await rolePage.roleType('static');
|
||||
assert.dom('[data-test-component="empty-state"]').doesNotExist('Empty states go away');
|
||||
assert.dom('[data-test-input="username"]').exists('Username field appears for static role');
|
||||
|
|
|
|||
|
|
@ -88,10 +88,11 @@ module('Acceptance | database workflow', function (hooks) {
|
|||
label: 'Root rotation statements',
|
||||
value: `Default`,
|
||||
},
|
||||
{ label: 'Skip initial rotation on static roles', value: 'No' },
|
||||
];
|
||||
});
|
||||
test('create with rotate', async function (assert) {
|
||||
assert.expect(24);
|
||||
assert.expect(26);
|
||||
this.server.post('/:backend/rotate-root/:name', () => {
|
||||
assert.ok(true, 'rotate root called');
|
||||
new Response(204);
|
||||
|
|
@ -119,12 +120,16 @@ 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'
|
||||
? PAGE.infoRowValueDiv(label)
|
||||
: PAGE.infoRowValue(label);
|
||||
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
|
||||
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
|
||||
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
|
||||
});
|
||||
});
|
||||
test('create without rotate', async function (assert) {
|
||||
assert.expect(23);
|
||||
assert.expect(25);
|
||||
this.server.post('/:backend/rotate-root/:name', () => {
|
||||
assert.notOk(true, 'rotate root called when it should not have been');
|
||||
new Response(204);
|
||||
|
|
@ -152,12 +157,16 @@ 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'
|
||||
? PAGE.infoRowValueDiv(label)
|
||||
: PAGE.infoRowValue(label);
|
||||
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
|
||||
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
|
||||
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
|
||||
});
|
||||
});
|
||||
test('create failure', async function (assert) {
|
||||
assert.expect(25);
|
||||
assert.expect(27);
|
||||
this.server.post('/:backend/rotate-root/:name', (schema, req) => {
|
||||
const okay = req.params.name !== 'bad-connection';
|
||||
assert.ok(okay, 'rotate root called but not for bad-connection');
|
||||
|
|
@ -192,8 +201,12 @@ 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'
|
||||
? PAGE.infoRowValueDiv(label)
|
||||
: PAGE.infoRowValue(label);
|
||||
assert.dom(PAGE.infoRowLabel(label)).hasText(label, `Label for ${label} is correct`);
|
||||
assert.dom(PAGE.infoRowValue(label)).hasText(value, `Value for ${label} is correct`);
|
||||
assert.dom(valueSelector).hasText(value, `Value for ${label} is correct`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ 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-input="skip_import_rotation"]');
|
||||
await click('[data-test-toggle-input="toggle-skip_import_rotation"]');
|
||||
|
||||
await click('[data-test-secret-save]');
|
||||
|
||||
await render(hbs`<DatabaseRoleEdit @model={{this.modelStatic}} @mode="show"/>`);
|
||||
|
|
|
|||
|
|
@ -11,59 +11,45 @@ import hbs from 'htmlbars-inline-precompile';
|
|||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
// default case should show all possible fields for each type
|
||||
pluginType: '',
|
||||
staticRoleFields: ['name', 'username', 'rotation_period', 'rotation_statements'],
|
||||
dynamicRoleFields: [
|
||||
'name',
|
||||
'default_ttl',
|
||||
'max_ttl',
|
||||
'creation_statements',
|
||||
'revocation_statements',
|
||||
'rollback_statements',
|
||||
'renew_statements',
|
||||
],
|
||||
},
|
||||
{
|
||||
pluginType: 'elasticsearch-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statement', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'mongodb-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statement', 'revocation_statement', 'default_ttl', 'max_ttl'],
|
||||
statementsHidden: true,
|
||||
},
|
||||
{
|
||||
pluginType: 'mssql-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'mysql-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'mysql-aurora-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'mysql-rds-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'mysql-legacy-database-plugin',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
{
|
||||
pluginType: 'vault-plugin-database-oracle',
|
||||
staticRoleFields: ['username', 'rotation_period'],
|
||||
staticRoleFields: ['username', 'rotation_period', 'skip_import_rotation'],
|
||||
dynamicRoleFields: ['creation_statements', 'revocation_statements', 'default_ttl', 'max_ttl'],
|
||||
},
|
||||
];
|
||||
|
|
@ -73,6 +59,7 @@ const ALL_ATTRS = [
|
|||
{ name: 'default_ttl', type: 'string', options: {} },
|
||||
{ name: 'max_ttl', type: 'string', options: {} },
|
||||
{ name: 'username', type: 'string', options: {} },
|
||||
{ name: 'skip_import_rotation', type: 'boolean', options: {} },
|
||||
{ name: 'rotation_period', type: 'string', options: {} },
|
||||
{ name: 'creation_statements', type: 'string', options: {} },
|
||||
{ name: 'creation_statement', type: 'string', options: {} },
|
||||
|
|
@ -114,20 +101,19 @@ module('Integration | Component | database-role-setting-form', function (hooks)
|
|||
|
||||
test('it shows appropriate fields based on roleType and db plugin', async function (assert) {
|
||||
this.set('roleType', 'static');
|
||||
this.set('dbType', '');
|
||||
this.set('dbParams', { plugin_name: '', skip_static_role_rotation_import: false });
|
||||
await render(hbs`
|
||||
<DatabaseRoleSettingForm
|
||||
@attrs={{this.model.attrs}}
|
||||
@model={{this.model}}
|
||||
@roleType={{this.roleType}}
|
||||
@dbType={{this.dbType}}
|
||||
@dbParams={{this.dbParams}}
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-component="empty-state"]').doesNotExist('Does not show empty states');
|
||||
for (const testCase of testCases) {
|
||||
const staticFields = getFields(testCase.staticRoleFields);
|
||||
const dynamicFields = getFields(testCase.dynamicRoleFields);
|
||||
this.set('dbType', testCase.pluginType);
|
||||
this.set('dbParams', { plugin_name: testCase.pluginType, skip_static_role_rotation_import: false });
|
||||
this.set('roleType', 'static');
|
||||
staticFields.show.forEach((attr) => {
|
||||
assert
|
||||
|
|
|
|||
|
|
@ -106,6 +106,26 @@ module('Integration | Component | form field', function (hooks) {
|
|||
assert.ok(spy.calledWith('foo', 'hello'), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: toggleButton', async function (assert) {
|
||||
const [model, spy] = await setup.call(
|
||||
this,
|
||||
createAttr('foobar', 'toggleButton', {
|
||||
defaultValue: false,
|
||||
editType: 'toggleButton',
|
||||
helperTextEnabled: 'Toggled on',
|
||||
helperTextDisabled: 'Toggled off',
|
||||
})
|
||||
);
|
||||
assert.ok(component.hasToggleButton, 'renders a toggle button');
|
||||
assert.dom('[data-test-toggle-input]').isNotChecked();
|
||||
assert.dom('[data-test-toggle-subtext]').hasText('Toggled off');
|
||||
|
||||
await component.fields.objectAt(0).toggleButton();
|
||||
|
||||
assert.true(model.get('foobar'));
|
||||
assert.ok(spy.calledWith('foobar', true), 'onChange called with correct args');
|
||||
});
|
||||
|
||||
test('it renders: editType file', async function (assert) {
|
||||
const subText = 'My subtext.';
|
||||
await setup.call(this, createAttr('foo', 'string', { editType: 'file', subText, docLink: '/docs' }));
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export default {
|
|||
hasStringList: isPresent('[data-test-component=string-list]'),
|
||||
hasTextFile: isPresent('[data-test-component=text-file]'),
|
||||
hasTTLPicker: isPresent('[data-test-toggle-input="Foo"]'),
|
||||
hasToggleButton: isPresent('[data-test-toggle-input="toggle-foobar"]'),
|
||||
hasJSONEditor: isPresent('[data-test-component="code-mirror-modifier"]'),
|
||||
hasJSONClearButton: isPresent('[data-test-json-clear-button]'),
|
||||
hasInput: isPresent('input'),
|
||||
|
|
@ -36,6 +37,7 @@ export default {
|
|||
fields: collection('[data-test-field]', {
|
||||
clickLabel: clickable('label'),
|
||||
toggleTtl: clickable('[data-test-toggle-input="Foo"]'),
|
||||
toggleButton: clickable('[data-test-toggle-input="toggle-foobar"]'),
|
||||
labelValue: text('[data-test-form-field-label]'),
|
||||
input: fillable('input'),
|
||||
ttlTime: fillable('[data-test-ttl-value]'),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ module('Unit | Serializer | database/connection', function (hooks) {
|
|||
url: 'http://localhost:9200',
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
skip_static_role_rotation_import: false,
|
||||
tls_ca: 'some-value',
|
||||
ca_cert: undefined, // does not send undefined values
|
||||
});
|
||||
|
|
@ -38,6 +39,7 @@ module('Unit | Serializer | database/connection', function (hooks) {
|
|||
url: 'http://localhost:9200',
|
||||
username: 'elastic',
|
||||
password: 'changeme',
|
||||
skip_static_role_rotation_import: false,
|
||||
insecure: false,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue