Skip to content

Commit

Permalink
ui: add capabilities to pki key model (#18412)
Browse files Browse the repository at this point in the history
* add capabilities to pki key model

* move key list from route into component

* rename test file

* rename test file

* add tests

* pass capabilities directly to key list componente

* add test for key list component

* rename test files

* remove href assertion
  • Loading branch information
hellobontempo authored Dec 16, 2022
1 parent d25bcaf commit 6413a7a
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 87 deletions.
26 changes: 26 additions & 0 deletions ui/app/models/pki/key.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Model, { attr } from '@ember-data/model';
import { inject as service } from '@ember/service';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { withFormFields } from 'vault/decorators/model-form-fields';
import { withModelValidations } from 'vault/decorators/model-validations';

Expand Down Expand Up @@ -40,4 +41,29 @@ export default class PkiKeyModel extends Model {
get backend() {
return this.secretMountPath.currentPath;
}

/* CAPABILITIES
* Default to show UI elements unless we know they can't access the given path
*/

@lazyCapabilities(apiPath`${'backend'}/key/${'key_id'}`, 'backend', 'key_id') keyPath;
get canRead() {
return this.keyPath.get('canRead') !== false;
}
get canEdit() {
return this.keyPath.get('canUpdate') !== false;
}
get canDelete() {
return this.keyPath.get('canDelete') !== false;
}

@lazyCapabilities(apiPath`${'backend'}/keys/generate`, 'backend') generatePath;
get canGenerateKey() {
return this.generatePath.get('canUpdate') !== false;
}

@lazyCapabilities(apiPath`${'backend'}/keys/import`, 'backend') importPath;
get canImportKey() {
return this.importPath.get('canUpdate') !== false;
}
}
30 changes: 17 additions & 13 deletions ui/lib/pki/addon/components/page/pki-key-details.hbs
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
<Toolbar>
<ToolbarActions>
<ConfirmAction
@buttonClasses="toolbar-link"
@onConfirmAction={{this.deleteKey}}
@confirmTitle="Delete key?"
@confirmButtonText="Delete"
data-test-pki-key-delete
>
Delete
</ConfirmAction>
<div class="toolbar-separator"></div>
{{#if @canDelete}}
<ConfirmAction
@buttonClasses="toolbar-link"
@onConfirmAction={{this.deleteKey}}
@confirmTitle="Delete key?"
@confirmButtonText="Delete"
data-test-pki-key-delete
>
Delete
</ConfirmAction>
<div class="toolbar-separator"></div>
{{/if}}
{{#if @key.privateKey}}
<DownloadButton class="toolbar-link" @filename={{this.model.name}} @data={{@key.privateKey}} @extension="pem">
Download private key
<Chevron @isButton={{true}} />
</DownloadButton>
{{/if}}
<ToolbarLink @route="keys.key.edit" @model={{@key.model.name}}>
Edit key
</ToolbarLink>
{{#if @canEdit}}
<ToolbarLink @route="keys.key.edit" @model={{@key.keyId}} data-test-pki-key-edit>
Edit key
</ToolbarLink>
{{/if}}
</ToolbarActions>
</Toolbar>

Expand Down
71 changes: 71 additions & 0 deletions ui/lib/pki/addon/components/page/pki-key-list.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<Toolbar>
<ToolbarActions>
{{#if @canImportKey}}
<ToolbarLink @route="keys.import" @type="download" data-test-pki-key-import>
Import
</ToolbarLink>
{{/if}}
{{#if @canGenerateKey}}
<ToolbarLink @route="keys.create" @type="add" data-test-pki-key-generate>
Generate
</ToolbarLink>
{{/if}}
</ToolbarActions>
</Toolbar>
<p class="has-padding">Below is information about the private keys used by the issuers to sign certificates. While
certificates represent a public assertion of an identity, private keys represent the private part of that identity, a
secret used to prove who they are and who they trust.</p>

{{#if @keyModels.length}}
{{#each @keyModels as |pkiKey|}}
<LinkedBlock class="list-item-row" @params={{array "keys.key.details" pkiKey.keyId}} @linkPrefix={{@mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline" data-test-key-id>
{{pkiKey.id}}
</span>
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiKey.keyName}}
<span class="tag has-text-grey-dark" data-test-key-name>{{pkiKey.keyName}}</span>
{{/if}}
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo
@route="keys.key.details"
@model={{pkiKey.keyId}}
@disabled={{not @canRead}}
data-test-key-menu-link="details"
>
Details
</LinkTo>
</li>
<li>
<LinkTo
@route="keys.key.edit"
@model={{pkiKey.keyId}}
@disabled={{not @canEdit}}
data-test-key-menu-link="edit"
>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
</LinkedBlock>
{{/each}}
{{else}}
<EmptyState @title="No keys yet" @message="There are no keys in this PKI mount. You can generate or create one." />
{{/if}}
21 changes: 10 additions & 11 deletions ui/lib/pki/addon/routes/keys/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Route from '@ember/routing/route';
import PkiOverviewRoute from '../overview';
import { inject as service } from '@ember/service';

export default class PkiKeysIndexRoute extends Route {
import { hash } from 'rsvp';
export default class PkiKeysIndexRoute extends PkiOverviewRoute {
@service store;
@service secretMountPath;
@service pathHelp;
Expand All @@ -12,18 +12,17 @@ export default class PkiKeysIndexRoute extends Route {
}

model() {
return this.store
.query('pki/key', { backend: this.secretMountPath.currentPath })
.then((keyModel) => {
return { keyModel, parentModel: this.modelFor('keys') };
})
.catch((err) => {
return hash({
hasConfig: this.hasConfig(),
parentModel: this.modelFor('keys'),
keyModels: this.store.query('pki/key', { backend: this.secretMountPath.currentPath }).catch((err) => {
if (err.httpStatus === 404) {
return { parentModel: this.modelFor('keys') };
return [];
} else {
throw err;
}
});
}),
});
}

setupController(controller, resolvedModel) {
Expand Down
63 changes: 10 additions & 53 deletions ui/lib/pki/addon/templates/keys/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,17 @@
}}
@isEngine={{true}}
/>
<Toolbar>
<ToolbarActions>
<ToolbarLink @route="keys.import" @type="download">
Import
</ToolbarLink>
<ToolbarLink @route="keys.create" @type="add">
Generate
</ToolbarLink>
</ToolbarActions>
</Toolbar>
<p class="has-padding">Below is information about the private keys used by the issuers to sign certificates. While
certificates represent a public assertion of an identity, private keys represent the private part of that identity, a
secret used to prove who they are and who they trust.</p>
{{#if this.model.keyModel.length}}
{{#each this.model.keyModel as |pkiKey|}}
<LinkedBlock class="list-item-row" @params={{array "keys.key.details" pkiKey.keyId}} @linkPrefix={{this.mountPoint}}>
<div class="level is-mobile">
<div class="level-left">
<div>
<Icon @name="certificate" class="has-text-grey-light" />
<span class="has-text-weight-semibold is-underline">
{{pkiKey.id}}
</span>
<div class="is-flex-row has-left-margin-l has-top-margin-xs">
{{#if pkiKey.keyName}}
<span class="tag has-text-grey-dark">{{pkiKey.keyName}}</span>
{{/if}}
</div>
</div>
</div>
<div class="level-right is-flex is-paddingless is-marginless">
<div class="level-item">
<PopupMenu>
<nav class="menu">
<ul class="menu-list">
<li>
<LinkTo @route="keys.key.details" @model={{pkiKey.keyId}}>
Details
</LinkTo>
</li>
<li>
<LinkTo @route="keys.key.edit" @model={{pkiKey.keyId}}>
Edit
</LinkTo>
</li>
</ul>
</nav>
</PopupMenu>
</div>
</div>
</div>
</LinkedBlock>
{{/each}}
{{#if this.model.hasConfig}}
<Page::PkiKeyList
@keyModels={{this.model.keyModels}}
@mountPoint={{this.mountPoint}}
@canImportKey={{this.model.keyModels.firstObject.canImportKey}}
@canGenerateKey={{this.model.keyModels.firstObject.canGenerateKey}}
@canRead={{this.model.keyModels.firstObject.canRead}}
@canEdit={{this.model.keyModels.firstObject.canEdit}}
/>
{{else}}
<Toolbar />
<EmptyState @title="PKI not configured" @message="This PKI mount hasn’t yet been configured with a certificate issuer.">
<LinkTo @route="configuration.create">
Configure PKI
Expand Down
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
Expand Up @@ -10,4 +10,4 @@
</p.levelLeft>
</PageHeader>

<Page::PkiKeyDetails @key={{this.model}} />
<Page::PkiKeyDetails @key={{this.model}} @canDelete={{this.model.canDelete}} @canEdit={{this.model.canEdit}} />
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const SELECTORS = {
keyTypeValue: '[data-test-value-div="Key type"]',
keyBitsValue: '[data-test-value-div="Key bits"]',
keyDeleteButton: '[data-test-pki-key-delete] button',
keyEditLink: '[data-test-pki-key-edit]',
confirmDelete: '[data-test-confirm-button]',
};
9 changes: 9 additions & 0 deletions ui/tests/helpers/pki/page/pki-key-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const SELECTORS = {
importKey: '[data-test-pki-key-import]',
generateKey: '[data-test-pki-key-generate]',
keyId: '[data-test-key-id]',
keyName: '[data-test-key-name]',
popupMenuTrigger: '[data-test-popup-menu-trigger]',
popupMenuDetails: '[data-test-key-menu-link="details"]',
popupMenuEdit: '[data-test-key-menu-link="edit"]',
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion ui/tests/helpers/pki/workflow.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SELECTORS as ROLEFORM } from './roles/form';
import { SELECTORS as ROLEFORM } from './pki-role-form';
import { SELECTORS as GENERATECERT } from './pki-role-generate';
export const SELECTORS = {
breadcrumbContainer: '[data-test-breadcrumbs]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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';
import { SELECTORS } from 'vault/tests/helpers/pki/page/pki-key-details';

module('Integration | Component | pki key details page', function (hooks) {
setupRenderingTest(hooks);
Expand All @@ -27,14 +27,18 @@ module('Integration | Component | pki key details page', function (hooks) {
});

test('it renders the page component and deletes a key', async function (assert) {
assert.expect(6);
assert.expect(7);
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}} />
<Page::PkiKeyDetails
@key={{this.model}}
@canDelete={{true}}
@canEdit={{true}}
/>
`,
{ owner: this.engine }
);
Expand All @@ -43,8 +47,27 @@ module('Integration | Component | pki key details page', function (hooks) {
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.keyEditLink).exists('renders edit link');
assert.dom(SELECTORS.keyDeleteButton).exists('renders delete button');
await click(SELECTORS.keyDeleteButton);
await click(SELECTORS.confirmDelete);
});

test('it does not render actions when capabilities are false', async function (assert) {
assert.expect(2);

await render(
hbs`
<Page::PkiKeyDetails
@key={{this.model}}
@canDelete={{false}}
@canEdit={{false}}
/>
`,
{ owner: this.engine }
);

assert.dom(SELECTORS.keyDeleteButton).doesNotExist('does not render delete button if no permission');
assert.dom(SELECTORS.keyEditLink).doesNotExist('does not render edit button if no permission');
});
});
Loading

0 comments on commit 6413a7a

Please sign in to comment.