UI/Database Secrets Engine cleanup (#10949)

* Update role toolbar, serialization for special mongo values

* Only show defaultShown if no value on info table row

* Remove root_rotation_statements from mongo connection fields

* Wrap this.router in try/catch if in then statement

* Add changelog
This commit is contained in:
Chelsea Shaw 2021-02-19 14:04:51 -06:00 committed by GitHub
parent 6f3d179635
commit 889d82aca5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 49 deletions

3
changelog/10949.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: Customize MongoDB input fields on Database Secrets Engine
```

View File

@ -52,7 +52,7 @@ export default class DatabaseListItem extends Component {
adapter
.rotateRootCredentials(backend, id)
.then(() => {
this.flashMessages.success(`Success: ${id} connection was reset`);
this.flashMessages.success(`Success: ${id} connection was rotated`);
})
.catch(e => {
this.flashMessages.danger(e.errors);

View File

@ -43,7 +43,11 @@ export default class DatabaseRoleEdit extends Component {
secret
.destroyRecord()
.then(() => {
this.router.transitionTo(LIST_ROOT_ROUTE, backend, { queryParams: { tab: 'role' } });
try {
this.router.transitionTo(LIST_ROOT_ROUTE, backend, { queryParams: { tab: 'role' } });
} catch (e) {
console.debug(e);
}
})
.catch(e => {
this.flashMessages.danger(e.errors?.join('. '));
@ -59,7 +63,11 @@ export default class DatabaseRoleEdit extends Component {
let path = roleSecret.type === 'static' ? 'static-roles' : 'roles';
roleSecret.set('path', path);
roleSecret.save().then(() => {
this.router.transitionTo(SHOW_ROUTE, `role/${secretId}`);
try {
this.router.transitionTo(SHOW_ROUTE, `role/${secretId}`);
} catch (e) {
console.debug(e);
}
});
}
@ -75,7 +83,11 @@ export default class DatabaseRoleEdit extends Component {
roleSecret.set('path', path);
}
roleSecret.save().then(() => {
this.router.transitionTo(SHOW_ROUTE, `role/${secretId}`);
try {
this.router.transitionTo(SHOW_ROUTE, `role/${secretId}`);
} catch (e) {
console.debug(e);
}
});
}
}

View File

@ -35,7 +35,7 @@ const STATEMENT_FIELDS = {
},
dynamic: {
default: ['creation_statements', 'revocation_statements', 'rotation_statements'],
'mongodb-database-plugin': ['creation_statement'],
'mongodb-database-plugin': ['creation_statement', 'revocation_statement'],
},
};
@ -44,25 +44,21 @@ export default class DatabaseRoleSettingForm extends Component {
const type = this.args.roleType;
if (!type) return null;
const db = this.args.dbType || 'default';
const fields = ROLE_FIELDS[type][db];
if (!Array.isArray(fields)) return fields;
const filtered = this.args.attrs.filter(a => {
const includes = fields.includes(a.name);
return includes;
const dbValidFields = ROLE_FIELDS[type][db];
if (!Array.isArray(dbValidFields)) return dbValidFields;
return this.args.attrs.filter(a => {
return dbValidFields.includes(a.name);
});
return filtered;
}
get statementFields() {
const type = this.args.roleType;
if (!type) return null;
const db = this.args.dbType || 'default';
const fields = STATEMENT_FIELDS[type][db];
if (!Array.isArray(fields)) return fields;
const filtered = this.args.attrs.filter(a => {
const includes = fields.includes(a.name);
return includes;
const dbValidFields = STATEMENT_FIELDS[type][db];
if (!Array.isArray(dbValidFields)) return dbValidFields;
return this.args.attrs.filter(a => {
return dbValidFields.includes(a.name);
});
return filtered;
}
}

View File

@ -79,10 +79,9 @@ export default Model.extend({
subText: 'x509 CA file for validating the certificate presented by the MongoDB server.',
editType: 'file',
}),
root_rotation_statements: attr('string', {
root_rotation_statements: attr({
subText: `The database statements to be executed to rotate the root user's credentials. If nothing is entered, Vault will use a reasonable default.`,
editType: 'json',
theme: 'hashi short',
editType: 'stringArray',
defaultShown: 'Default',
}),
@ -115,15 +114,7 @@ export default Model.extend({
// for both create and edit fields
mainFields: computed('plugin_name', function() {
return [
'plugin_name',
'name',
'connection_url',
'verify_connection',
'password_policy',
'pluginConfig',
'root_rotation_statements',
];
return ['plugin_name', 'name', 'connection_url', 'verify_connection', 'password_policy', 'pluginConfig'];
}),
showAttrs: computed('plugin_name', function() {
@ -133,13 +124,15 @@ export default Model.extend({
'connection_url',
'write_concern',
'verify_connection',
'root_rotation_statements',
'allowed_roles',
];
return expandAttributeMeta(this, f);
}),
pluginFieldGroups: computed('plugin_name', function() {
if (!this.plugin_name) {
return null;
}
let groups = [{ default: ['username', 'password', 'write_concern'] }];
// TODO: Get plugin options based on plugin
groups.push({

View File

@ -64,7 +64,7 @@ export default Model.extend({
theme: 'hashi short',
defaultShown: 'Default',
}),
rotation_statement: attr('string', {
revocation_statement: attr('string', {
editType: 'json',
theme: 'hashi short',
defaultShown: 'Default',
@ -72,7 +72,18 @@ export default Model.extend({
/* FIELD ATTRIBUTES */
get fieldAttrs() {
let fields = ['database', 'name', 'type'];
// Main fields on edit/create form
let fields = ['name', 'database', 'type'];
return expandAttributeMeta(this, fields);
},
get showFields() {
let fields = ['name', 'database', 'type'];
if (this.type === 'dynamic') {
fields = fields.concat(['ttl', 'max_ttl', 'creation_statements', 'revocation_statements']);
} else {
fields = fields.concat(['username', 'rotation_period']);
}
return expandAttributeMeta(this, fields);
},
@ -86,8 +97,8 @@ export default Model.extend({
'creation_statements',
'creation_statement', // only for MongoDB (styling difference)
'revocation_statements',
'revocation_statement', // only for MongoDB (styling difference)
'rotation_statements',
'rotation_statement', // only for MongoDB (styling difference)
];
return expandAttributeMeta(this, allRoleSettingFields);
}),

View File

@ -24,12 +24,17 @@ export default RESTSerializer.extend({
if (payload.data.db_name) {
database = [payload.data.db_name];
}
// Copy to singular for MongoDB
const creation_statement = payload.data.creation_statements[0];
const revocation_statement = payload.data.revocation_statements[0];
return {
id: payload.secret,
name: payload.secret,
backend: payload.backend,
database,
path,
creation_statement,
revocation_statement,
...payload.data,
};
},
@ -64,6 +69,18 @@ export default RESTSerializer.extend({
data.db_name = db;
delete data.database;
}
// This is necessary because the input for MongoDB is a json string
// rather than an array, so we transpose that here
if (data.creation_statement) {
const singleStatement = data.creation_statement;
data.creation_statements = [singleStatement];
delete data.creation_statement;
}
if (data.revocation_statement) {
const singleStatement = data.revocation_statement;
data.revocation_statements = [singleStatement];
delete data.revocation_statement;
}
return data;
},

View File

@ -87,7 +87,7 @@
{{!-- Plugin Config Section --}}
<div class="form-section">
<h3 class="title is-5">Plugin config</h3>
{{#unless @model.plugin_name}}
{{#unless @model.pluginFieldGroups}}
<EmptyState
@title="No plugin selected"
@message="Select a plugin type to be able to configure it."
@ -97,7 +97,6 @@
{{#each-in fieldGroup as |group fields|}}
{{#if (eq group "default")}}
{{#each fields as |attr|}}
{{!-- TODO: special password edit mode --}}
{{form-field data-test-field attr=attr model=@model}}
{{/each}}
{{else}}

View File

@ -18,6 +18,19 @@
{{#if (eq @mode 'show')}}
<Toolbar>
<ToolbarActions>
{{#if @model.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@onConfirmAction={{action 'delete'}}
@confirmTitle="Delete role?"
@confirmMessage="This role will be permanently deleted. You will need to recreate it to use it again."
@confirmButtonText="Delete"
data-test-database-role-delete
>
Delete role
</ConfirmAction>
<div class="toolbar-separator" />
{{/if}}
{{#if @model.canGenerateCredentials}}
<button
type="button"
@ -28,18 +41,6 @@
Generate credentials
</button>
{{/if}}
{{#if @model.canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@onConfirmAction={{action 'delete'}}
@confirmTitle="Delete role?"
@confirmMessage="This role will be permanently deleted. You will need to re-create it to use it again."
@confirmButtonText="Delete"
data-test-database-role-delete
>
Delete role
</ConfirmAction>
{{/if}}
{{#if @model.canEditRole}}
<ToolbarSecretLink
@secret={{concat 'role/' @model.id}}
@ -53,7 +54,7 @@
{{/if}}
</ToolbarActions>
</Toolbar>
{{#each @model.fieldAttrs as |attr|}}
{{#each @model.showFields as |attr|}}
{{#let attr.options.defaultDisplay as |defaultDisplay|}}
{{#if (eq attr.type "object")}}
<InfoTableRow

View File

@ -28,7 +28,7 @@
@glyph="cancel-square-outline"
/> No
{{/if}}
{{else if (and alwaysRender defaultShown)}}
{{else if (and (not value) (and alwaysRender defaultShown))}}
{{defaultShown}}
{{else}}
{{#if (eq type 'array')}}