mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	UI: VAULT-9409 Pki Tidy Form (#20043)
This commit is contained in:
		
							
								
								
									
										36
									
								
								ui/app/adapters/pki/tidy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								ui/app/adapters/pki/tidy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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) }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								ui/app/models/pki/tidy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ui/app/models/pki/tidy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
| } | ||||
| @@ -13,7 +13,7 @@ | ||||
|       </button> | ||||
|       <div class="toolbar-separator"></div> | ||||
|     {{/if}} | ||||
|     <ToolbarLink @route="configuration.tidy"> | ||||
|     <ToolbarLink @route="configuration.tidy" data-test-tidy-toolbar> | ||||
|       Tidy | ||||
|     </ToolbarLink> | ||||
|     <ToolbarLink @route="configuration.edit"> | ||||
|   | ||||
							
								
								
									
										83
									
								
								ui/lib/pki/addon/components/page/pki-tidy-form.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								ui/lib/pki/addon/components/page/pki-tidy-form.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| <PageHeader as |p|> | ||||
|   <p.top> | ||||
|     <Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} /> | ||||
|   </p.top> | ||||
|   <p.levelLeft> | ||||
|     <h1 class="title is-3"> | ||||
|       <Icon @name="pki" @size="24" class="has-text-grey-light" /> | ||||
|       Tidy | ||||
|     </h1> | ||||
|   </p.levelLeft> | ||||
| </PageHeader> | ||||
|  | ||||
| <hr class="is-marginless has-background-gray-200" /> | ||||
|  | ||||
| <p class="has-top-margin-m has-bottom-margin-l">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.</p> | ||||
|  | ||||
| <MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" /> | ||||
|  | ||||
| <form class="has-bottom-margin-s" {{on "submit" (perform this.save)}}> | ||||
|   <div class="has-bottom-margin-s"> | ||||
|     <Input | ||||
|       @type="checkbox" | ||||
|       @checked={{@tidy.tidyCertStore}} | ||||
|       id="tidy-certificate-store" | ||||
|       {{on "input" (fn (mut @tidy.tidyCertStore) (not @tidy.tidyCertStore))}} | ||||
|       data-test-tidy-cert-store-checkbox | ||||
|     /> | ||||
|  | ||||
|     <label for="tidy-certificate-store" class="is-label" data-test-tidy-cert-store-label> | ||||
|       Tidy the certificate store | ||||
|     </label> | ||||
|   </div> | ||||
|   <div class="has-bottom-margin-s"> | ||||
|     <Input | ||||
|       @type="checkbox" | ||||
|       @checked={{@tidy.tidyRevocationQueue}} | ||||
|       id="tidy-revocation-queue" | ||||
|       {{on "input" (fn (mut @tidy.tidyRevocationQueue) (not @tidy.tidyRevocationQueue))}} | ||||
|       data-test-tidy-revocation-queue-checkbox | ||||
|     /> | ||||
|  | ||||
|     <label for="tidy-revocation-queue" class="is-label" data-test-tidy-revocation-queue-label> | ||||
|       Tidy the revocation list (CRL) | ||||
|     </label> | ||||
|   </div> | ||||
|  | ||||
|   <TtlPicker | ||||
|     class="has-top-margin-l has-bottom-margin-l" | ||||
|     @initialValue={{@tidy.safetyBuffer}} | ||||
|     @onChange={{this.updateSafetyBuffer}} | ||||
|     @hideToggle={{true}} | ||||
|     @label="Safety buffer" | ||||
|     @helperTextEnabled="For a certificate to be expunged, the time must be after the expiration time of the certificate (according to the local | ||||
|     clock) plus the safety buffer. The default is 72 hours." | ||||
|   /> | ||||
|   <hr class="is-marginless has-background-gray-200" /> | ||||
|  | ||||
|   <div class="has-top-margin-m"> | ||||
|     <button | ||||
|       type="submit" | ||||
|       class="button is-primary {{if this.save.isRunning 'is-loading'}}" | ||||
|       disabled={{this.save.isRunning}} | ||||
|       data-test-pki-tidy-button | ||||
|     > | ||||
|       Tidy | ||||
|     </button> | ||||
|     <button | ||||
|       type="button" | ||||
|       class="button is-secondary" | ||||
|       disabled={{this.save.isRunning}} | ||||
|       {{on "click" this.cancel}} | ||||
|       data-test-pki-tidy-cancel | ||||
|     > | ||||
|       Cancel | ||||
|     </button> | ||||
|     {{#if this.invalidFormAlert}} | ||||
|       <div class="control"> | ||||
|         <AlertInline @type="danger" @paddingTop={{true}} @message={{this.invalidFormAlert}} @mimicRefresh={{true}} /> | ||||
|       </div> | ||||
|     {{/if}} | ||||
|   </div> | ||||
| </form> | ||||
							
								
								
									
										54
									
								
								ui/lib/pki/addon/components/page/pki-tidy-form.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								ui/lib/pki/addon/components/page/pki-tidy-form.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<Args> { | ||||
|   @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(); | ||||
|   } | ||||
| } | ||||
| @@ -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' }, | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| configuration.tidy | ||||
| <Page::PkiTidyForm @breadcrumbs={{this.breadcrumbs}} @tidy={{this.model}} @adapterOptions={{hash tidyType="manual-tidy"}} /> | ||||
| @@ -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 () { | ||||
| @@ -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) { | ||||
|   | ||||
							
								
								
									
										17
									
								
								ui/tests/helpers/pki/page/pki-tidy-form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ui/tests/helpers/pki/page/pki-tidy-form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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]', | ||||
| }; | ||||
| @@ -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, | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -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`<Page::PkiTidyForm @tidy={{this.tidy}} @breadcrumbs={{this.breadcrumbs}} />`, { | ||||
|       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`<Page::PkiTidyForm @tidy={{this.tidy}} @breadcrumbs={{this.breadcrumbs}} />`, { | ||||
|       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'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										61
									
								
								ui/tests/unit/adapters/pki/tidy-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								ui/tests/unit/adapters/pki/tidy-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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' } }); | ||||
|   }); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user
	 Kianna
					Kianna