ui: delete pki key functionality (#18146)
* add deletekey * fix types * move page components into folder * finish tests * make linting changes * declare flashmessages ts service * restructure pki test files * add delete test * add more folders
This commit is contained in:
parent
e75633eddc
commit
5f79edc49c
|
@ -1,9 +1,7 @@
|
||||||
import ApplicationAdapter from '../application';
|
import ApplicationAdapter from '../application';
|
||||||
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||||
|
|
||||||
export default class PkiKeyAdapter extends ApplicationAdapter {
|
export default class PkiKeyAdapter extends ApplicationAdapter {
|
||||||
namespace = 'v1';
|
namespace = 'v1';
|
||||||
|
|
||||||
getUrl(backend, id) {
|
getUrl(backend, id) {
|
||||||
const url = `${this.buildURL()}/${encodePath(backend)}`;
|
const url = `${this.buildURL()}/${encodePath(backend)}`;
|
||||||
if (id) {
|
if (id) {
|
||||||
|
@ -21,4 +19,9 @@ export default class PkiKeyAdapter extends ApplicationAdapter {
|
||||||
const { backend, id } = query;
|
const { backend, id } = query;
|
||||||
return this.ajax(this.getUrl(backend, id), 'GET');
|
return this.ajax(this.getUrl(backend, id), 'GET');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteRecord(store, type, snapshot) {
|
||||||
|
const { id, record } = snapshot;
|
||||||
|
return this.ajax(this.getUrl(record.backend, id), 'DELETE');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import Model, { attr } from '@ember-data/model';
|
import Model, { attr } from '@ember-data/model';
|
||||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
import { inject as service } from '@ember/service';
|
||||||
|
import { withFormFields } from 'vault/decorators/model-form-fields';
|
||||||
|
|
||||||
|
@withFormFields(['keyId', 'keyName', 'keyType', 'keyBits'])
|
||||||
export default class PkiKeyModel extends Model {
|
export default class PkiKeyModel extends Model {
|
||||||
@attr('string', { readOnly: true }) backend;
|
@service secretMountPath;
|
||||||
|
|
||||||
@attr('boolean') isDefault;
|
@attr('boolean') isDefault;
|
||||||
@attr('string', { possibleValues: ['internal', 'external'] }) type;
|
@attr('string', { possibleValues: ['internal', 'external'] }) type;
|
||||||
@attr('string', { detailsLabel: 'Key ID' }) keyId;
|
@attr('string', { detailsLabel: 'Key ID' }) keyId;
|
||||||
@attr('string') keyName;
|
@attr('string') keyName;
|
||||||
@attr('string') keyType;
|
@attr('string') keyType;
|
||||||
@attr('string', { detailsLabel: 'Key bit length' }) keyBits; // TODO confirm with crypto team to remove this field from details page
|
@attr('string') keyBits;
|
||||||
|
|
||||||
// TODO refactor when field-to-attrs util is refactored as decorator
|
get backend() {
|
||||||
constructor() {
|
return this.secretMountPath.currentPath;
|
||||||
super(...arguments);
|
|
||||||
this.formFields = expandAttributeMeta(this, ['keyId', 'keyName', 'keyType', 'keyBits']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import FlashMessages from 'ember-cli-flash/services/flash-messages';
|
import FlashMessages from 'ember-cli-flash/services/flash-messages';
|
||||||
|
|
||||||
export default FlashMessages.extend({
|
export default class FlashMessageService extends FlashMessages {
|
||||||
stickyInfo(message) {
|
stickyInfo(message: string) {
|
||||||
return this.info(message, {
|
return this.info(message, {
|
||||||
sticky: true,
|
sticky: true,
|
||||||
priority: 300,
|
priority: 300,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
||||||
export default function (error, fallbackMessage = 'An error occurred, please try again') {
|
export default function (error, fallbackMessage = 'An error occurred, please try again') {
|
||||||
if (error?.errors) {
|
if (error instanceof Error && error?.errors) {
|
||||||
return error.errors.join(', ');
|
return error.errors.join(', ');
|
||||||
}
|
}
|
||||||
return error?.message || fallbackMessage;
|
return error?.message || fallbackMessage;
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import RouterService from '@ember/routing/router-service';
|
||||||
|
import FlashMessageService from 'vault/services/flash-messages';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import errorMessage from 'vault/utils/error-message';
|
||||||
|
interface Args {
|
||||||
|
key: {
|
||||||
|
rollbackAttributes: () => void;
|
||||||
|
destroyRecord: () => void;
|
||||||
|
backend: string;
|
||||||
|
keyName: string;
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PkiKeyDetails extends Component<Args> {
|
||||||
|
@service declare readonly router: RouterService;
|
||||||
|
@service declare readonly flashMessages: FlashMessageService;
|
||||||
|
|
||||||
|
get breadcrumbs() {
|
||||||
|
return [
|
||||||
|
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||||
|
{ label: this.args.key.backend || 'pki', route: 'overview' },
|
||||||
|
{ label: 'keys', route: 'keys.index' },
|
||||||
|
{ label: this.args.key.keyId },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async deleteKey() {
|
||||||
|
try {
|
||||||
|
await this.args.key.destroyRecord();
|
||||||
|
this.flashMessages.success('Key deleted successfully');
|
||||||
|
this.router.transitionTo('vault.cluster.secrets.backend.pki.keys.index');
|
||||||
|
} catch (error) {
|
||||||
|
this.args.key.rollbackAttributes();
|
||||||
|
this.flashMessages.danger(errorMessage(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import { action } from '@ember/object';
|
|
||||||
import Component from '@glimmer/component';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
interface Args {
|
|
||||||
key: {
|
|
||||||
backend: string;
|
|
||||||
keyName: string;
|
|
||||||
keyId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PkiKeyDetails extends Component<Args> {
|
|
||||||
@service declare secretMountPath: { currentPath: string };
|
|
||||||
|
|
||||||
get breadcrumbs() {
|
|
||||||
return [
|
|
||||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
|
||||||
{ label: this.secretMountPath.currentPath || 'pki', route: 'overview' },
|
|
||||||
{ label: 'keys', route: 'keys.index' },
|
|
||||||
{ label: this.args.key.keyId },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@action deleteKey() {
|
|
||||||
// TODO handle delete
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +1 @@
|
||||||
<PkiKeyDetails @key={{this.model}} />
|
<Page::PkiKeyDetails @key={{this.model}} />
|
|
@ -1 +1 @@
|
||||||
<PkiRoleDetailsPage @role={{this.model}} />
|
<Page::PkiRoleDetails @role={{this.model}} />
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const SELECTORS = {
|
||||||
|
breadcrumbContainer: '[data-test-breadcrumbs]',
|
||||||
|
breadcrumbs: '[data-test-breadcrumbs] li',
|
||||||
|
title: '[data-test-key-details-title]',
|
||||||
|
keyIdValue: '[data-test-value-div="Key ID"]',
|
||||||
|
keyNameValue: '[data-test-value-div="Key name"]',
|
||||||
|
keyTypeValue: '[data-test-value-div="Key type"]',
|
||||||
|
keyBitsValue: '[data-test-value-div="Key bits"]',
|
||||||
|
keyDeleteButton: '[data-test-pki-key-delete] button',
|
||||||
|
confirmDelete: '[data-test-confirm-button]',
|
||||||
|
};
|
|
@ -1,7 +1,6 @@
|
||||||
export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;
|
export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;
|
||||||
|
|
||||||
export const SELECTORS = {
|
export const SELECTORS = {
|
||||||
// Pki role
|
|
||||||
roleName: '[data-test-input="name"]',
|
roleName: '[data-test-input="name"]',
|
||||||
issuerRef: '[data-test-input="issuerRef"]',
|
issuerRef: '[data-test-input="issuerRef"]',
|
||||||
customTtl: '[data-test-field="customTtl"]',
|
customTtl: '[data-test-field="customTtl"]',
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { click, render } from '@ember/test-helpers';
|
||||||
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
import { SELECTORS } from 'vault/tests/helpers/pki/keys/page-details';
|
||||||
|
|
||||||
|
module('Integration | Component | pki key details page', function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
setupEngine(hooks, 'pki');
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.owner.lookup('service:flash-messages').registerTypes(['success', 'danger']);
|
||||||
|
this.store = this.owner.lookup('service:store');
|
||||||
|
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||||
|
this.backend = 'pki-test';
|
||||||
|
this.secretMountPath.currentPath = this.backend;
|
||||||
|
this.store.pushPayload('pki/key', {
|
||||||
|
modelName: 'pki/key',
|
||||||
|
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
|
||||||
|
key_type: 'ec',
|
||||||
|
key_name: 'test-key',
|
||||||
|
});
|
||||||
|
this.model = this.store.peekRecord('pki/key', '724862ff-6438-bad0-b598-77a6c7f4e934');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders the page component and deletes a key', async function (assert) {
|
||||||
|
assert.expect(9);
|
||||||
|
this.server.delete(`${this.backend}/key/${this.model.keyId}`, () => {
|
||||||
|
assert.ok(true, 'confirming delete fires off destroyRecord()');
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`
|
||||||
|
<Page::PkiKeyDetails @key={{this.model}} />
|
||||||
|
`,
|
||||||
|
{ owner: this.engine }
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumb containers exist');
|
||||||
|
assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs');
|
||||||
|
assert.dom(SELECTORS.title).containsText('View key', 'title renders');
|
||||||
|
assert.dom(SELECTORS.keyIdValue).hasText(' 724862ff-6438-bad0-b598-77a6c7f4e934', 'key id renders');
|
||||||
|
assert.dom(SELECTORS.keyNameValue).hasText('test-key', 'key name renders');
|
||||||
|
assert.dom(SELECTORS.keyTypeValue).hasText('ec', 'key type renders');
|
||||||
|
assert.dom(SELECTORS.keyBitsValue).doesNotExist('does not render empty value');
|
||||||
|
assert.dom(SELECTORS.keyDeleteButton).exists('renders delete button');
|
||||||
|
await click(SELECTORS.keyDeleteButton);
|
||||||
|
await click(SELECTORS.confirmDelete);
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, fillIn } from '@ember/test-helpers';
|
import { render, fillIn } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { setupEngine } from 'ember-engines/test-support';
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||||
|
|
||||||
module('Integration | Component | pki-key-parameters', function (hooks) {
|
module('Integration | Component | pki-key-parameters', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, click, findAll } from '@ember/test-helpers';
|
import { render, click, findAll } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { setupEngine } from 'ember-engines/test-support';
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||||
|
|
||||||
module('Integration | Component | pki-key-usage', function (hooks) {
|
module('Integration | Component | pki-key-usage', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, click, fillIn, find } from '@ember/test-helpers';
|
import { render, click, fillIn, find } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { setupEngine } from 'ember-engines/test-support';
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
|
import { SELECTORS } from 'vault/tests/helpers/pki/roles/form';
|
||||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
|
||||||
module('Integration | Component | pki-role-form', function (hooks) {
|
module('Integration | Component | pki-role-form', function (hooks) {
|
|
@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render } from '@ember/test-helpers';
|
import { render } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { setupEngine } from 'ember-engines/test-support';
|
import { setupEngine } from 'ember-engines/test-support';
|
||||||
import { SELECTORS } from 'vault/tests/helpers/pki/page-role-details';
|
import { SELECTORS } from 'vault/tests/helpers/pki/roles/page-details';
|
||||||
|
|
||||||
module('Integration | Component | pki role details page', function (hooks) {
|
module('Integration | Component | pki role details page', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
@ -24,7 +24,7 @@ module('Integration | Component | pki role details page', function (hooks) {
|
||||||
assert.expect(7);
|
assert.expect(7);
|
||||||
await render(
|
await render(
|
||||||
hbs`
|
hbs`
|
||||||
<PkiRoleDetailsPage @role={{this.model}} />
|
<Page::PkiRoleDetails @role={{this.model}} />
|
||||||
`,
|
`,
|
||||||
{ owner: this.engine }
|
{ owner: this.engine }
|
||||||
);
|
);
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
|
||||||
|
module('Unit | Adapter | pki/key', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.store = this.owner.lookup('service:store');
|
||||||
|
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
|
||||||
|
this.backend = 'pki-test';
|
||||||
|
this.secretMountPath.currentPath = this.backend;
|
||||||
|
this.data = {
|
||||||
|
key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
|
||||||
|
key_type: 'ec',
|
||||||
|
key_name: 'test-key',
|
||||||
|
key_bits: '256',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
hooks.afterEach(function () {
|
||||||
|
this.server.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should make request to correct endpoint on query', async function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
const { key_id, ...otherAttrs } = this.data; // excludes key_id from key_info data
|
||||||
|
const key_info = { [key_id]: { ...otherAttrs } };
|
||||||
|
this.server.get(`${this.backend}/keys`, (schema, req) => {
|
||||||
|
assert.strictEqual(req.queryParams.list, 'true', 'request is made to correct endpoint on query');
|
||||||
|
return { data: { keys: [key_id], key_info } };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store.query('pki/key', { backend: this.backend });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should make request to correct endpoint on queryRecord', async function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => {
|
||||||
|
assert.ok(true, 'request is made to correct endpoint on query record');
|
||||||
|
return { data: this.data };
|
||||||
|
});
|
||||||
|
|
||||||
|
this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should make request to correct endpoint on delete', async function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
this.store.pushPayload('pki/key', { modelName: 'pki/key', ...this.data });
|
||||||
|
this.server.get(`${this.backend}/key/${this.data.key_id}`, () => ({ data: this.data }));
|
||||||
|
this.server.delete(`${this.backend}/key/${this.data.key_id}`, () => {
|
||||||
|
assert.ok(true, 'request made to correct endpoint on delete');
|
||||||
|
});
|
||||||
|
|
||||||
|
const model = await this.store.queryRecord('pki/key', { backend: this.backend, id: this.data.key_id });
|
||||||
|
await model.destroyRecord();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
declare module 'ember-cli-flash/services/flash-messages' {
|
||||||
|
import Service from '@ember/service';
|
||||||
|
import FlashObject from 'ember-cli-flash/flash/object';
|
||||||
|
import { A } from '@ember/array';
|
||||||
|
|
||||||
|
type Partial<T> = { [K in keyof T]?: T[K] };
|
||||||
|
|
||||||
|
interface MessageOptions {
|
||||||
|
type: string;
|
||||||
|
priority: number;
|
||||||
|
timeout: number;
|
||||||
|
sticky: boolean;
|
||||||
|
showProgress: boolean;
|
||||||
|
extendedTimeout: number;
|
||||||
|
destroyOnClick: boolean;
|
||||||
|
onDestroy: () => void;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomMessageInfo extends Partial<MessageOptions> {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FlashFunction {
|
||||||
|
(message: string, options?: Partial<MessageOptions>): FlashMessageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlashMessageService extends Service {
|
||||||
|
queue: A<FlashObject>;
|
||||||
|
success: FlashFunction;
|
||||||
|
warning: FlashFunction;
|
||||||
|
info: FlashFunction;
|
||||||
|
error: FlashFunction;
|
||||||
|
danger: FlashFunction;
|
||||||
|
alert: FlashFunction;
|
||||||
|
secondary: FlashFunction;
|
||||||
|
add(messageInfo: CustomMessageInfo): FlashMessageService;
|
||||||
|
clearMessages(): FlashMessageService;
|
||||||
|
registerTypes(types: string[]): FlashMessageService;
|
||||||
|
getFlashObject(): FlashObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FlashMessageService;
|
||||||
|
}
|
Loading…
Reference in New Issue