Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: delete pki key functionality #18146

Merged
merged 10 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions ui/app/adapters/pki/key.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import ApplicationAdapter from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default class PkiKeyAdapter extends ApplicationAdapter {
namespace = 'v1';

getUrl(backend, id) {
const url = `${this.buildURL()}/${encodePath(backend)}`;
if (id) {
Expand All @@ -21,4 +19,9 @@ export default class PkiKeyAdapter extends ApplicationAdapter {
const { backend, id } = query;
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');
}
}
15 changes: 8 additions & 7 deletions ui/app/models/pki/key.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
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'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ 🤩

export default class PkiKeyModel extends Model {
@attr('string', { readOnly: true }) backend;
@service secretMountPath;

@attr('boolean') isDefault;
@attr('string', { possibleValues: ['internal', 'external'] }) type;
@attr('string', { detailsLabel: 'Key ID' }) keyId;
@attr('string') keyName;
@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
constructor() {
super(...arguments);
this.formFields = expandAttributeMeta(this, ['keyId', 'keyName', 'keyType', 'keyBits']);
get backend() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it bad for this to be a getter? In other situations we've added it as an attribute in the adapter - but I felt it sometimes got lost there.

Happy to move it there instead!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh I love this! I wonder if we can standardize everywhere else 👀

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to use alias on the service's currentPath but I think it's potato potato

return this.secretMountPath.currentPath;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import FlashMessages from 'ember-cli-flash/services/flash-messages';

export default FlashMessages.extend({
stickyInfo(message) {
export default class FlashMessageService extends FlashMessages {
stickyInfo(message: string) {
return this.info(message, {
sticky: true,
priority: 300,
});
},
});
}
}
2 changes: 1 addition & 1 deletion ui/app/utils/error-message.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// 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') {
if (error?.errors) {
if (error instanceof Error && error?.errors) {
return error.errors.join(', ');
}
return error?.message || fallbackMessage;
Expand Down
41 changes: 41 additions & 0 deletions ui/lib/pki/addon/components/page/pki-key-details.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
27 changes: 0 additions & 27 deletions ui/lib/pki/addon/components/pki-key-details.ts

This file was deleted.

2 changes: 1 addition & 1 deletion ui/lib/pki/addon/templates/keys/key/details.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<PkiKeyDetails @key={{this.model}} />
<Page::PkiKeyDetails @key={{this.model}} />
2 changes: 1 addition & 1 deletion ui/lib/pki/addon/templates/roles/role/details.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<PkiRoleDetailsPage @role={{this.model}} />
<Page::PkiRoleDetails @role={{this.model}} />
11 changes: 11 additions & 0 deletions ui/tests/helpers/pki/keys/page-details.js
Original file line number Diff line number Diff line change
@@ -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]',
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;

export const SELECTORS = {
// Pki role
roleName: '[data-test-input="name"]',
issuerRef: '[data-test-input="issuerRef"]',
customTtl: '[data-test-field="customTtl"]',
Expand Down
53 changes: 53 additions & 0 deletions ui/tests/integration/components/pki/keys/page-details-test.js
Original file line number Diff line number Diff line change
@@ -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/key/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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
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) {
setupRenderingTest(hooks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, click, findAll } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
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) {
setupRenderingTest(hooks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
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';

module('Integration | Component | pki-role-form', function (hooks) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
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) {
setupRenderingTest(hooks);
Expand All @@ -24,7 +24,7 @@ module('Integration | Component | pki role details page', function (hooks) {
assert.expect(7);
await render(
hbs`
<PkiRoleDetailsPage @role={{this.model}} />
<Page::PkiRoleDetails @role={{this.model}} />
`,
{ owner: this.engine }
);
Expand Down
60 changes: 60 additions & 0 deletions ui/tests/unit/adapters/pki/key-test.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
44 changes: 44 additions & 0 deletions ui/types/ember-cli-flash/services/flash-messages.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}