diff --git a/ui/app/models/pki/key.js b/ui/app/models/pki/key.js
index fe41d51b03a6..b428200bc850 100644
--- a/ui/app/models/pki/key.js
+++ b/ui/app/models/pki/key.js
@@ -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';
@@ -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;
+ }
}
diff --git a/ui/lib/pki/addon/components/page/pki-key-details.hbs b/ui/lib/pki/addon/components/page/pki-key-details.hbs
index 2552c91c77e5..c828da2ec47b 100644
--- a/ui/lib/pki/addon/components/page/pki-key-details.hbs
+++ b/ui/lib/pki/addon/components/page/pki-key-details.hbs
@@ -1,24 +1,28 @@
-
- Delete
-
-
+ {{#if @canDelete}}
+
+ Delete
+
+
+ {{/if}}
{{#if @key.privateKey}}
Download private key
{{/if}}
-
- Edit key
-
+ {{#if @canEdit}}
+
+ Edit key
+
+ {{/if}}
diff --git a/ui/lib/pki/addon/components/page/pki-key-list.hbs b/ui/lib/pki/addon/components/page/pki-key-list.hbs
new file mode 100644
index 000000000000..5fecda49ca74
--- /dev/null
+++ b/ui/lib/pki/addon/components/page/pki-key-list.hbs
@@ -0,0 +1,71 @@
+
+
+ {{#if @canImportKey}}
+
+ Import
+
+ {{/if}}
+ {{#if @canGenerateKey}}
+
+ Generate
+
+ {{/if}}
+
+
+
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.
+
+{{#if @keyModels.length}}
+ {{#each @keyModels as |pkiKey|}}
+
+
+
+
+
+
+ {{pkiKey.id}}
+
+
+ {{#if pkiKey.keyName}}
+ {{pkiKey.keyName}}
+ {{/if}}
+
+
+
+
+
+
+ {{/each}}
+{{else}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/lib/pki/addon/routes/keys/index.js b/ui/lib/pki/addon/routes/keys/index.js
index 24a0cc0109d5..4995fb946319 100644
--- a/ui/lib/pki/addon/routes/keys/index.js
+++ b/ui/lib/pki/addon/routes/keys/index.js
@@ -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;
@@ -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) {
diff --git a/ui/lib/pki/addon/templates/keys/index.hbs b/ui/lib/pki/addon/templates/keys/index.hbs
index 1159275b23f6..fbca0dd5a1b0 100644
--- a/ui/lib/pki/addon/templates/keys/index.hbs
+++ b/ui/lib/pki/addon/templates/keys/index.hbs
@@ -8,60 +8,17 @@
}}
@isEngine={{true}}
/>
-
-
-
- Import
-
-
- Generate
-
-
-
-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.
-{{#if this.model.keyModel.length}}
- {{#each this.model.keyModel as |pkiKey|}}
-
-
-
-
-
-
- {{pkiKey.id}}
-
-
- {{#if pkiKey.keyName}}
- {{pkiKey.keyName}}
- {{/if}}
-
-
-
-
-
-
- {{/each}}
+{{#if this.model.hasConfig}}
+
{{else}}
+
Configure PKI
diff --git a/ui/lib/pki/addon/templates/keys/key/details.hbs b/ui/lib/pki/addon/templates/keys/key/details.hbs
index 450f46fb6be7..1f44f2b3fcbf 100644
--- a/ui/lib/pki/addon/templates/keys/key/details.hbs
+++ b/ui/lib/pki/addon/templates/keys/key/details.hbs
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/tests/helpers/pki/keys/page-details.js b/ui/tests/helpers/pki/page/pki-key-details.js
similarity index 91%
rename from ui/tests/helpers/pki/keys/page-details.js
rename to ui/tests/helpers/pki/page/pki-key-details.js
index 1be50fc05ee0..b353205629d7 100644
--- a/ui/tests/helpers/pki/keys/page-details.js
+++ b/ui/tests/helpers/pki/page/pki-key-details.js
@@ -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]',
};
diff --git a/ui/tests/helpers/pki/page/pki-key-list.js b/ui/tests/helpers/pki/page/pki-key-list.js
new file mode 100644
index 000000000000..79196e1eda73
--- /dev/null
+++ b/ui/tests/helpers/pki/page/pki-key-list.js
@@ -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"]',
+};
diff --git a/ui/tests/helpers/pki/roles/page-details.js b/ui/tests/helpers/pki/page/pki-role-details.js
similarity index 100%
rename from ui/tests/helpers/pki/roles/page-details.js
rename to ui/tests/helpers/pki/page/pki-role-details.js
diff --git a/ui/tests/helpers/pki/keys/form.js b/ui/tests/helpers/pki/pki-key-form.js
similarity index 100%
rename from ui/tests/helpers/pki/keys/form.js
rename to ui/tests/helpers/pki/pki-key-form.js
diff --git a/ui/tests/helpers/pki/roles/form.js b/ui/tests/helpers/pki/pki-role-form.js
similarity index 100%
rename from ui/tests/helpers/pki/roles/form.js
rename to ui/tests/helpers/pki/pki-role-form.js
diff --git a/ui/tests/helpers/pki/workflow.js b/ui/tests/helpers/pki/workflow.js
index 6ce1454570aa..f204754bc33e 100644
--- a/ui/tests/helpers/pki/workflow.js
+++ b/ui/tests/helpers/pki/workflow.js
@@ -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]',
diff --git a/ui/tests/integration/components/pki/keys/page-details-test.js b/ui/tests/integration/components/pki/page/pki-key-details-test.js
similarity index 69%
rename from ui/tests/integration/components/pki/keys/page-details-test.js
rename to ui/tests/integration/components/pki/page/pki-key-details-test.js
index 090b1c61bd2b..48562f7dcae7 100644
--- a/ui/tests/integration/components/pki/keys/page-details-test.js
+++ b/ui/tests/integration/components/pki/page/pki-key-details-test.js
@@ -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);
@@ -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`
-
+
`,
{ owner: this.engine }
);
@@ -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`
+
+ `,
+ { 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');
+ });
});
diff --git a/ui/tests/integration/components/pki/page/pki-key-list-test.js b/ui/tests/integration/components/pki/page/pki-key-list-test.js
new file mode 100644
index 000000000000..f82e24cc8f3a
--- /dev/null
+++ b/ui/tests/integration/components/pki/page/pki-key-list-test.js
@@ -0,0 +1,101 @@
+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/page/pki-key-list';
+
+module('Integration | Component | pki key list page', function (hooks) {
+ setupRenderingTest(hooks);
+ setupEngine(hooks, 'pki');
+ setupMirage(hooks);
+
+ hooks.beforeEach(function () {
+ this.store = this.owner.lookup('service:store');
+ this.secretMountPath = this.owner.lookup('service:secret-mount-path');
+ this.secretMountPath.currentPath = 'pki-test';
+ this.store.pushPayload('pki/key', {
+ modelName: 'pki/key',
+ key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
+ key_type: 'ec',
+ key_name: 'test-key',
+ });
+ this.store.pushPayload('pki/key', {
+ modelName: 'pki/key',
+ key_id: '9fdddf12-9ce3-0268-6b34-dc1553b00175',
+ key_type: 'rsa',
+ key_name: 'another-key',
+ });
+ this.keyModels = this.store.peekAll('pki/key');
+ });
+
+ test('it renders empty state when no keys exist', async function (assert) {
+ assert.expect(3);
+ this.keyModels = [];
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert
+ .dom('[data-test-empty-state-title]')
+ .hasText('No keys yet', 'renders empty state that no keys exist');
+ assert.dom(SELECTORS.importKey).exists('renders toolbar with import action');
+ assert.dom(SELECTORS.generateKey).exists('renders toolbar with generate action');
+ });
+
+ test('it renders list of keys and actions when permission allowed', async function (assert) {
+ assert.expect(6);
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert
+ .dom(SELECTORS.keyId)
+ .hasText('724862ff-6438-bad0-b598-77a6c7f4e934', 'linked block renders key id');
+ assert.dom(SELECTORS.keyName).hasText('test-key', 'linked block renders key name');
+ assert.dom(SELECTORS.importKey).exists('renders import action');
+ assert.dom(SELECTORS.generateKey).exists('renders generate action');
+ await click(SELECTORS.popupMenuTrigger);
+ assert.dom(SELECTORS.popupMenuDetails).exists('details link exists');
+ assert.dom(SELECTORS.popupMenuEdit).exists('edit link exists');
+ });
+
+ test('it hides or disables actions when permission denied', async function (assert) {
+ assert.expect(4);
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert.dom(SELECTORS.importKey).doesNotExist('renders import action');
+ assert.dom(SELECTORS.generateKey).doesNotExist('renders generate action');
+ await click(SELECTORS.popupMenuTrigger);
+ assert.dom(SELECTORS.popupMenuDetails).hasClass('disabled', 'details link enabled');
+ assert.dom(SELECTORS.popupMenuEdit).hasClass('disabled', 'edit link enabled');
+ });
+});
diff --git a/ui/tests/integration/components/pki/roles/page-pki-role-details-test.js b/ui/tests/integration/components/pki/page/pki-role-details-test.js
similarity index 96%
rename from ui/tests/integration/components/pki/roles/page-pki-role-details-test.js
rename to ui/tests/integration/components/pki/page/pki-role-details-test.js
index d27fa8278b9a..0134abd1a6b8 100644
--- a/ui/tests/integration/components/pki/roles/page-pki-role-details-test.js
+++ b/ui/tests/integration/components/pki/page/pki-role-details-test.js
@@ -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/roles/page-details';
+import { SELECTORS } from 'vault/tests/helpers/pki/page/pki-role-details';
module('Integration | Component | pki role details page', function (hooks) {
setupRenderingTest(hooks);
diff --git a/ui/tests/integration/components/pki/keys/form-test.js b/ui/tests/integration/components/pki/pki-key-form-test.js
similarity index 98%
rename from ui/tests/integration/components/pki/keys/form-test.js
rename to ui/tests/integration/components/pki/pki-key-form-test.js
index 568df1e4f330..dd3821276b06 100644
--- a/ui/tests/integration/components/pki/keys/form-test.js
+++ b/ui/tests/integration/components/pki/pki-key-form-test.js
@@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, click, 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/keys/form';
+import { SELECTORS } from 'vault/tests/helpers/pki/pki-key-form';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Integration | Component | pki key form', function (hooks) {
diff --git a/ui/tests/integration/components/pki/pki-key-list-test.js b/ui/tests/integration/components/pki/pki-key-list-test.js
new file mode 100644
index 000000000000..f82e24cc8f3a
--- /dev/null
+++ b/ui/tests/integration/components/pki/pki-key-list-test.js
@@ -0,0 +1,101 @@
+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/page/pki-key-list';
+
+module('Integration | Component | pki key list page', function (hooks) {
+ setupRenderingTest(hooks);
+ setupEngine(hooks, 'pki');
+ setupMirage(hooks);
+
+ hooks.beforeEach(function () {
+ this.store = this.owner.lookup('service:store');
+ this.secretMountPath = this.owner.lookup('service:secret-mount-path');
+ this.secretMountPath.currentPath = 'pki-test';
+ this.store.pushPayload('pki/key', {
+ modelName: 'pki/key',
+ key_id: '724862ff-6438-bad0-b598-77a6c7f4e934',
+ key_type: 'ec',
+ key_name: 'test-key',
+ });
+ this.store.pushPayload('pki/key', {
+ modelName: 'pki/key',
+ key_id: '9fdddf12-9ce3-0268-6b34-dc1553b00175',
+ key_type: 'rsa',
+ key_name: 'another-key',
+ });
+ this.keyModels = this.store.peekAll('pki/key');
+ });
+
+ test('it renders empty state when no keys exist', async function (assert) {
+ assert.expect(3);
+ this.keyModels = [];
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert
+ .dom('[data-test-empty-state-title]')
+ .hasText('No keys yet', 'renders empty state that no keys exist');
+ assert.dom(SELECTORS.importKey).exists('renders toolbar with import action');
+ assert.dom(SELECTORS.generateKey).exists('renders toolbar with generate action');
+ });
+
+ test('it renders list of keys and actions when permission allowed', async function (assert) {
+ assert.expect(6);
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert
+ .dom(SELECTORS.keyId)
+ .hasText('724862ff-6438-bad0-b598-77a6c7f4e934', 'linked block renders key id');
+ assert.dom(SELECTORS.keyName).hasText('test-key', 'linked block renders key name');
+ assert.dom(SELECTORS.importKey).exists('renders import action');
+ assert.dom(SELECTORS.generateKey).exists('renders generate action');
+ await click(SELECTORS.popupMenuTrigger);
+ assert.dom(SELECTORS.popupMenuDetails).exists('details link exists');
+ assert.dom(SELECTORS.popupMenuEdit).exists('edit link exists');
+ });
+
+ test('it hides or disables actions when permission denied', async function (assert) {
+ assert.expect(4);
+ await render(
+ hbs`
+ ,
+ `,
+ { owner: this.engine }
+ );
+ assert.dom(SELECTORS.importKey).doesNotExist('renders import action');
+ assert.dom(SELECTORS.generateKey).doesNotExist('renders generate action');
+ await click(SELECTORS.popupMenuTrigger);
+ assert.dom(SELECTORS.popupMenuDetails).hasClass('disabled', 'details link enabled');
+ assert.dom(SELECTORS.popupMenuEdit).hasClass('disabled', 'edit link enabled');
+ });
+});
diff --git a/ui/tests/integration/components/pki/pki-key-parameters-test.js b/ui/tests/integration/components/pki/pki-key-parameters-test.js
index b5fdd87232f7..07ab94be3a9e 100644
--- a/ui/tests/integration/components/pki/pki-key-parameters-test.js
+++ b/ui/tests/integration/components/pki/pki-key-parameters-test.js
@@ -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/roles/form';
+import { SELECTORS } from 'vault/tests/helpers/pki/pki-role-form';
module('Integration | Component | pki key parameters', function (hooks) {
setupRenderingTest(hooks);
diff --git a/ui/tests/integration/components/pki/pki-key-usage-test.js b/ui/tests/integration/components/pki/pki-key-usage-test.js
index e290030c7b35..64122f2bb496 100644
--- a/ui/tests/integration/components/pki/pki-key-usage-test.js
+++ b/ui/tests/integration/components/pki/pki-key-usage-test.js
@@ -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/roles/form';
+import { SELECTORS } from 'vault/tests/helpers/pki/pki-role-form';
module('Integration | Component | pki-key-usage', function (hooks) {
setupRenderingTest(hooks);
diff --git a/ui/tests/integration/components/pki/roles/form-test.js b/ui/tests/integration/components/pki/pki-role-form-test.js
similarity index 98%
rename from ui/tests/integration/components/pki/roles/form-test.js
rename to ui/tests/integration/components/pki/pki-role-form-test.js
index 2704b7fa9c28..2065b3f0f168 100644
--- a/ui/tests/integration/components/pki/roles/form-test.js
+++ b/ui/tests/integration/components/pki/pki-role-form-test.js
@@ -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/roles/form';
+import { SELECTORS } from 'vault/tests/helpers/pki/pki-role-form';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Integration | Component | pki-role-form', function (hooks) {