From f3a232cd5577c17c47544f28a681ebf37c62db52 Mon Sep 17 00:00:00 2001 From: Kianna <30884335+kiannaquach@users.noreply.github.com> Date: Mon, 10 Apr 2023 23:07:26 -0700 Subject: [PATCH] UI: VAULT-9409 Pki Tidy Form (#20043) --- ui/app/adapters/pki/tidy.js | 36 ++++++++ ui/app/models/pki/tidy.js | 12 +++ .../page/pki-configuration-details.hbs | 2 +- .../addon/components/page/pki-tidy-form.hbs | 83 +++++++++++++++++++ .../addon/components/page/pki-tidy-form.ts | 54 ++++++++++++ ui/lib/pki/addon/routes/configuration/tidy.js | 22 ++++- .../addon/templates/configuration/tidy.hbs | 2 +- ...tion-test.js => pki-configuration-test.js} | 2 +- .../pki/pki-engine-workflow-test.js | 21 +++++ ui/tests/helpers/pki/page/pki-tidy-form.js | 17 ++++ ui/tests/helpers/pki/workflow.js | 2 + .../components/pki/page/pki-tidy-form-test.js | 58 +++++++++++++ ui/tests/unit/adapters/pki/tidy-test.js | 61 ++++++++++++++ 13 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 ui/app/adapters/pki/tidy.js create mode 100644 ui/app/models/pki/tidy.js create mode 100644 ui/lib/pki/addon/components/page/pki-tidy-form.hbs create mode 100644 ui/lib/pki/addon/components/page/pki-tidy-form.ts rename ui/tests/acceptance/pki/{configuration-test.js => pki-configuration-test.js} (99%) create mode 100644 ui/tests/helpers/pki/page/pki-tidy-form.js create mode 100644 ui/tests/integration/components/pki/page/pki-tidy-form-test.js create mode 100644 ui/tests/unit/adapters/pki/tidy-test.js diff --git a/ui/app/adapters/pki/tidy.js b/ui/app/adapters/pki/tidy.js new file mode 100644 index 0000000000..6657f8368b --- /dev/null +++ b/ui/app/adapters/pki/tidy.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ +import { assert } from '@ember/debug'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; +import ApplicationAdapter from '../application'; + +export default class PkiTidyAdapter extends ApplicationAdapter { + namespace = 'v1'; + + urlForCreateRecord(snapshot) { + const { backend } = snapshot.record; + const { tidyType } = snapshot.adapterOptions; + + if (!backend) { + throw new Error('Backend missing'); + } + + const baseUrl = `${this.buildURL()}/${encodePath(backend)}`; + + switch (tidyType) { + case 'manual-tidy': + return `${baseUrl}/tidy`; + case 'auto-tidy': + return `${baseUrl}/config/auto-tidy`; + default: + assert('type must be one of manual-tidy, auto-tidy'); + } + } + + createRecord(store, type, snapshot) { + const url = this.urlForCreateRecord(snapshot); + return this.ajax(url, 'POST', { data: this.serialize(snapshot) }); + } +} diff --git a/ui/app/models/pki/tidy.js b/ui/app/models/pki/tidy.js new file mode 100644 index 0000000000..e1c722a1af --- /dev/null +++ b/ui/app/models/pki/tidy.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Model, { attr } from '@ember-data/model'; + +export default class PkiTidyModel extends Model { + @attr('boolean', { defaultValue: false }) tidyCertStore; + @attr('boolean', { defaultValue: false }) tidyRevocationQueue; + @attr('string', { defaultValue: '72h' }) safetyBuffer; +} diff --git a/ui/lib/pki/addon/components/page/pki-configuration-details.hbs b/ui/lib/pki/addon/components/page/pki-configuration-details.hbs index af53330b27..21f970ea78 100644 --- a/ui/lib/pki/addon/components/page/pki-configuration-details.hbs +++ b/ui/lib/pki/addon/components/page/pki-configuration-details.hbs @@ -13,7 +13,7 @@
{{/if}} - + Tidy diff --git a/ui/lib/pki/addon/components/page/pki-tidy-form.hbs b/ui/lib/pki/addon/components/page/pki-tidy-form.hbs new file mode 100644 index 0000000000..6573aca76c --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-tidy-form.hbs @@ -0,0 +1,83 @@ + + + + + +

+ + Tidy +

+
+
+ +
+ +

Tidying cleans up the storage backend and/or CRL by removing certificates + that have expired and are past a certain buffer period beyond their expiration time.

+ + + +
+
+ + + +
+
+ + + +
+ + +
+ +
+ + + {{#if this.invalidFormAlert}} +
+ +
+ {{/if}} +
+ \ No newline at end of file diff --git a/ui/lib/pki/addon/components/page/pki-tidy-form.ts b/ui/lib/pki/addon/components/page/pki-tidy-form.ts new file mode 100644 index 0000000000..ed4e0d75b1 --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-tidy-form.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Component from '@glimmer/component'; +import errorMessage from 'vault/utils/error-message'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { task } from 'ember-concurrency'; +import { waitFor } from '@ember/test-waiters'; +import { tracked } from '@glimmer/tracking'; + +import PkiTidyModel from 'vault/models/pki/tidy'; +import RouterService from '@ember/routing/router-service'; + +interface Args { + tidy: PkiTidyModel; + adapterOptions: object; +} + +export default class PkiTidyForm extends Component { + @service declare readonly router: RouterService; + + @tracked errorBanner = ''; + @tracked invalidFormAlert = ''; + + returnToConfiguration() { + this.router.transitionTo('vault.cluster.secrets.backend.pki.configuration.index'); + } + + @action + updateSafetyBuffer({ goSafeTimeString }: { goSafeTimeString: string }) { + this.args.tidy.safetyBuffer = goSafeTimeString; + } + + @task + @waitFor + *save(event: Event) { + event.preventDefault(); + try { + yield this.args.tidy.save({ adapterOptions: this.args.adapterOptions }); + this.returnToConfiguration(); + } catch (e) { + this.errorBanner = errorMessage(e); + this.invalidFormAlert = 'There was an error submitting this form.'; + } + } + + @action + cancel() { + this.returnToConfiguration(); + } +} diff --git a/ui/lib/pki/addon/routes/configuration/tidy.js b/ui/lib/pki/addon/routes/configuration/tidy.js index 76c4fa0cb3..914123708a 100644 --- a/ui/lib/pki/addon/routes/configuration/tidy.js +++ b/ui/lib/pki/addon/routes/configuration/tidy.js @@ -4,5 +4,25 @@ */ import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { withConfirmLeave } from 'core/decorators/confirm-leave'; -export default class PkiConfigurationTidyRoute extends Route {} +@withConfirmLeave('model.tidy') +export default class PkiConfigurationTidyRoute extends Route { + @service store; + @service secretMountPath; + + model() { + return this.store.createRecord('pki/tidy', { backend: this.secretMountPath.currentPath }); + } + + setupController(controller, resolvedModel) { + super.setupController(controller, resolvedModel); + controller.breadcrumbs = [ + { label: 'secrets', route: 'secrets', linkExternal: true }, + { label: this.secretMountPath.currentPath, route: 'overview' }, + { label: 'configuration', route: 'configuration.index' }, + { label: 'tidy' }, + ]; + } +} diff --git a/ui/lib/pki/addon/templates/configuration/tidy.hbs b/ui/lib/pki/addon/templates/configuration/tidy.hbs index 0fcbb9beab..24d7612bb3 100644 --- a/ui/lib/pki/addon/templates/configuration/tidy.hbs +++ b/ui/lib/pki/addon/templates/configuration/tidy.hbs @@ -1 +1 @@ -configuration.tidy \ No newline at end of file + \ No newline at end of file diff --git a/ui/tests/acceptance/pki/configuration-test.js b/ui/tests/acceptance/pki/pki-configuration-test.js similarity index 99% rename from ui/tests/acceptance/pki/configuration-test.js rename to ui/tests/acceptance/pki/pki-configuration-test.js index 3d8e4152cc..0ce57b82a8 100644 --- a/ui/tests/acceptance/pki/configuration-test.js +++ b/ui/tests/acceptance/pki/pki-configuration-test.js @@ -16,7 +16,7 @@ import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands'; import { SELECTORS } from 'vault/tests/helpers/pki/workflow'; import { issuerPemBundle } from 'vault/tests/helpers/pki/values'; -module('Acceptance | pki configuration', function (hooks) { +module('Acceptance | pki configuration test', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(async function () { diff --git a/ui/tests/acceptance/pki/pki-engine-workflow-test.js b/ui/tests/acceptance/pki/pki-engine-workflow-test.js index b77149c3f1..4b1dda7b22 100644 --- a/ui/tests/acceptance/pki/pki-engine-workflow-test.js +++ b/ui/tests/acceptance/pki/pki-engine-workflow-test.js @@ -399,6 +399,27 @@ module('Acceptance | pki workflow', function (hooks) { .dom('[data-test-input="commonName"]') .hasValue('Hashicorp Test', 'form prefilled with parent issuer cn'); }); + + test('it navigates to the tidy page from configuration toolbar', async function (assert) { + await authPage.login(this.pkiAdminToken); + await visit(`/vault/secrets/${this.mountPath}/pki/configuration`); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`); + await click(SELECTORS.configuration.tidyToolbar); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/tidy`); + }); + + test('it returns to the configuration page after submit', async function (assert) { + await authPage.login(this.pkiAdminToken); + await visit(`/vault/secrets/${this.mountPath}/pki/configuration`); + await click(SELECTORS.configuration.tidyToolbar); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/tidy`); + await click(SELECTORS.configuration.tidyCertStoreCheckbox); + await click(SELECTORS.configuration.tidyRevocationCheckbox); + await fillIn(SELECTORS.configuration.safetyBufferInput, '100'); + await fillIn(SELECTORS.configuration.safetyBufferInputDropdown, 'd'); + await click(SELECTORS.configuration.tidySave); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`); + }); }); module('rotate', function (hooks) { diff --git a/ui/tests/helpers/pki/page/pki-tidy-form.js b/ui/tests/helpers/pki/page/pki-tidy-form.js new file mode 100644 index 0000000000..10ac46910a --- /dev/null +++ b/ui/tests/helpers/pki/page/pki-tidy-form.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export const SELECTORS = { + tidyCertStoreLabel: '[data-test-tidy-cert-store-label]', + tidyRevocationList: '[data-test-tidy-revocation-queue-label]', + safetyBufferTTL: '[data-test-ttl-inputs]', + tidyCertStoreCheckbox: '[data-test-tidy-cert-store-checkbox]', + tidyRevocationCheckbox: '[data-test-tidy-revocation-queue-checkbox]', + safetyBufferInput: '[data-test-ttl-value="Safety buffer"]', + safetyBufferInputDropdown: '[data-test-select="ttl-unit"]', + tidyToolbar: '[data-test-tidy-toolbar]', + tidySave: '[data-test-pki-tidy-button]', + tidyCancel: '[data-test-pki-tidy-cancel]', +}; diff --git a/ui/tests/helpers/pki/workflow.js b/ui/tests/helpers/pki/workflow.js index cf3eb267fe..5b56734d88 100644 --- a/ui/tests/helpers/pki/workflow.js +++ b/ui/tests/helpers/pki/workflow.js @@ -10,6 +10,7 @@ import { SELECTORS as KEYPAGES } from './page/pki-keys'; import { SELECTORS as ISSUERDETAILS } from './pki-issuer-details'; import { SELECTORS as CONFIGURATION } from './pki-configure-create'; import { SELECTORS as DELETE } from './pki-delete-all-issuers'; +import { SELECTORS as TIDY } from './page/pki-tidy-form'; export const SELECTORS = { breadcrumbContainer: '[data-test-breadcrumbs]', @@ -66,5 +67,6 @@ export const SELECTORS = { pkiBetaBannerLink: '[data-test-pki-configuration-banner] a', ...CONFIGURATION, ...DELETE, + ...TIDY, }, }; diff --git a/ui/tests/integration/components/pki/page/pki-tidy-form-test.js b/ui/tests/integration/components/pki/page/pki-tidy-form-test.js new file mode 100644 index 0000000000..5fc776019a --- /dev/null +++ b/ui/tests/integration/components/pki/page/pki-tidy-form-test.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, render, fillIn } 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-tidy-form'; + +module('Integration | Component | pki | Page::PkiTidyForm', 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.tidy = this.store.createRecord('pki/tidy', { backend: 'pki-test' }); + + this.breadcrumbs = [ + { label: 'secrets', route: 'secrets', linkExternal: true }, + { label: 'pki-test', route: 'overview' }, + { label: 'configuration', route: 'configuration.index' }, + { label: 'tidy' }, + ]; + }); + + test('it should render tidy fields', async function (assert) { + await render(hbs``, { + owner: this.engine, + }); + assert.dom(SELECTORS.tidyCertStoreLabel).hasText('Tidy the certificate store'); + assert.dom(SELECTORS.tidyRevocationList).hasText('Tidy the revocation list (CRL)'); + assert.dom(SELECTORS.safetyBufferTTL).exists(); + assert.dom(SELECTORS.safetyBufferInput).hasValue('3'); + assert.dom('[data-test-select="ttl-unit"]').hasValue('d'); + }); + + test('it should change the attributes on the model', async function (assert) { + await render(hbs``, { + owner: this.engine, + }); + await click(SELECTORS.tidyCertStoreCheckbox); + await click(SELECTORS.tidyRevocationCheckbox); + await fillIn(SELECTORS.safetyBufferInput, '5'); + assert.true(this.tidy.tidyCertStore); + assert.true(this.tidy.tidyRevocationQueue); + assert.dom(SELECTORS.safetyBufferInput).hasValue('5'); + assert.dom('[data-test-select="ttl-unit"]').hasValue('d'); + assert.strictEqual(this.tidy.safetyBuffer, '120h'); + }); +}); diff --git a/ui/tests/unit/adapters/pki/tidy-test.js b/ui/tests/unit/adapters/pki/tidy-test.js new file mode 100644 index 0000000000..b82dda4d20 --- /dev/null +++ b/ui/tests/unit/adapters/pki/tidy-test.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'vault/tests/helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; + +module('Unit | Adapter | pki/tidy', 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.server.post('/sys/capabilities-self', allowAllCapabilitiesStub()); + }); + + test('it exists', function (assert) { + const adapter = this.owner.lookup('adapter:pki/tidy'); + assert.ok(adapter); + }); + + test('it calls the correct endpoint when tidyType = manual-tidy', async function (assert) { + assert.expect(1); + + this.server.post(`${this.backend}/tidy`, () => { + assert.ok(true, 'request made to correct endpoint on create'); + return {}; + }); + this.payload = { + tidy_cert_store: true, + tidy_revocation_queue: false, + safetyBuffer: '120h', + backend: this.backend, + }; + await this.store + .createRecord('pki/tidy', this.payload) + .save({ adapterOptions: { tidyType: 'manual-tidy' } }); + }); + + test('it calls the correct endpoint when tidyType = auto-tidy', async function (assert) { + assert.expect(1); + this.server.post(`${this.backend}/config/auto-tidy`, () => { + assert.ok(true, 'request made to correct endpoint on create'); + return {}; + }); + this.payload = { + enabled: true, + interval_duration: '72h', + backend: this.backend, + }; + await this.store + .createRecord('pki/tidy', this.payload) + .save({ adapterOptions: { tidyType: 'auto-tidy' } }); + }); +});