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) {