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.
+
+
+
+
\ 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' } });
+ });
+});