From 55d18515c1b6fe5de32329aa12fa95c5a526ea7a Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:49:24 -0500 Subject: [PATCH] UI: standardize display for type=exported (#19672) --- ui/app/models/pki/action.js | 12 +- .../components/page/pki-configure-create.hbs | 84 ++++++ .../pki-configure-create.ts} | 12 +- .../page/pki-issuer-generate-intermediate.hbs | 17 ++ .../page/pki-issuer-generate-intermediate.js | 6 + .../page/pki-issuer-generate-root.hbs | 18 ++ .../page/pki-issuer-generate-root.js | 6 + .../components/page/pki-issuer-import.hbs | 18 ++ .../components/page/pki-issuer-import.js | 6 + .../addon/components/pki-configure-form.hbs | 69 ----- .../pki/addon/components/pki-generate-csr.ts | 25 +- .../addon/components/pki-generate-root.hbs | 187 +++++++----- ...-generate-root.js => pki-generate-root.ts} | 62 +++- .../components/pki-import-pem-bundle.hbs | 121 +++++--- .../addon/components/pki-import-pem-bundle.ts | 19 +- .../addon/templates/configuration/create.hbs | 14 +- .../issuers/generate-intermediate.hbs | 17 +- .../addon/templates/issuers/generate-root.hbs | 17 +- ui/lib/pki/addon/templates/issuers/import.hbs | 17 +- .../acceptance/pki/pki-action-forms-test.js | 274 ++++++++++++++++++ .../pki-engine-route-cleanup-test.js} | 10 +- .../pki/pki-engine-workflow-test.js | 70 ----- ...figure-form.js => pki-configure-create.js} | 8 +- ui/tests/helpers/pki/pki-generate-root.js | 11 + ui/tests/helpers/pki/workflow.js | 2 +- .../pki-configure-create-test.js} | 37 ++- .../pki-issuer-generate-intermediate-test.js | 80 +++++ .../pki/page/pki-issuer-generate-root-test.js | 77 +++++ .../pki/page/pki-issuer-import-test.js | 67 +++++ ui/types/vault/app-types.ts | 6 + ui/types/vault/models/pki/action.d.ts | 37 ++- 31 files changed, 1067 insertions(+), 339 deletions(-) create mode 100644 ui/lib/pki/addon/components/page/pki-configure-create.hbs rename ui/lib/pki/addon/components/{pki-configure-form.ts => page/pki-configure-create.ts} (81%) create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.hbs create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.js create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-generate-root.hbs create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-generate-root.js create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-import.hbs create mode 100644 ui/lib/pki/addon/components/page/pki-issuer-import.js delete mode 100644 ui/lib/pki/addon/components/pki-configure-form.hbs rename ui/lib/pki/addon/components/{pki-generate-root.js => pki-generate-root.ts} (55%) create mode 100644 ui/tests/acceptance/pki/pki-action-forms-test.js rename ui/tests/acceptance/{pki-engine-route-cleanupt-test.js => pki/pki-engine-route-cleanup-test.js} (98%) rename ui/tests/helpers/pki/{pki-configure-form.js => pki-configure-create.js} (63%) rename ui/tests/integration/components/pki/{pki-configure-form-test.js => page/pki-configure-create-test.js} (50%) create mode 100644 ui/tests/integration/components/pki/page/pki-issuer-generate-intermediate-test.js create mode 100644 ui/tests/integration/components/pki/page/pki-issuer-generate-root-test.js create mode 100644 ui/tests/integration/components/pki/page/pki-issuer-import-test.js diff --git a/ui/app/models/pki/action.js b/ui/app/models/pki/action.js index 8bd370dba..701db9922 100644 --- a/ui/app/models/pki/action.js +++ b/ui/app/models/pki/action.js @@ -24,6 +24,13 @@ const validations = { ], }; +/** + * This model maps to multiple PKI endpoints, specifically the ones that make up the + * configuration/create workflow. These endpoints also share a nontypical behavior in that + * a POST request to the endpoints don't necessarily result in a single entity created -- + * depending on the inputs, some number of issuers, keys, and certificates can be created + * from the API. + */ @withModelValidations(validations) @withFormFields() export default class PkiActionModel extends Model { @@ -37,6 +44,7 @@ export default class PkiActionModel extends Model { @attr importedIssuers; @attr importedKeys; @attr mapping; + @attr('string', { readOnly: true, masked: true }) certificate; /* actionType generate-root */ @attr('string', { @@ -176,12 +184,12 @@ export default class PkiActionModel extends Model { @attr('string') ttl; @attr('date') notAfter; - @attr('string', { readOnly: true }) issuerId; // returned from generate-root action + @attr('string', { label: 'Issuer ID', readOnly: true, detailLinkTo: 'issuers.issuer.details' }) issuerId; // returned from generate-root action // For generating and signing a CSR @attr('string', { label: 'CSR', masked: true }) csr; @attr caChain; - @attr('string', { label: 'Key ID' }) keyId; + @attr('string', { label: 'Key ID', detailLinkTo: 'keys.key.details' }) keyId; @attr('string', { masked: true }) privateKey; @attr('string') privateKeyType; diff --git a/ui/lib/pki/addon/components/page/pki-configure-create.hbs b/ui/lib/pki/addon/components/page/pki-configure-create.hbs new file mode 100644 index 000000000..1e72ed56b --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-configure-create.hbs @@ -0,0 +1,84 @@ + + + + + +

+ {{this.title}} +

+
+
+ +{{#unless @config.id}} +
+
+ {{#each this.configTypes as |option|}} +
+ +
+ {{/each}} +
+
+{{/unless}} +{{#if (eq @config.actionType "import")}} + +{{else if (eq @config.actionType "generate-root")}} + +{{else if (eq @config.actionType "generate-csr")}} + +{{else}} + +
+
+ + +
+
+{{/if}} \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-configure-form.ts b/ui/lib/pki/addon/components/page/pki-configure-create.ts similarity index 81% rename from ui/lib/pki/addon/components/pki-configure-form.ts rename to ui/lib/pki/addon/components/page/pki-configure-create.ts index 19e21a368..94c859a81 100644 --- a/ui/lib/pki/addon/components/pki-configure-form.ts +++ b/ui/lib/pki/addon/components/page/pki-configure-create.ts @@ -5,28 +5,34 @@ import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; // TYPES import Store from '@ember-data/store'; import Router from '@ember/routing/router'; import FlashMessageService from 'vault/services/flash-messages'; import PkiActionModel from 'vault/models/pki/action'; +import { Breadcrumb } from 'vault/vault/app-types'; interface Args { config: PkiActionModel; + onCancel: CallableFunction; + breadcrumbs: Breadcrumb; } /** - * @module PkiConfigureForm - * PkiConfigureForm component is used to configure a PKI engine mount. + * @module PkiConfigureCreate + * Page::PkiConfigureCreate component is used to configure a PKI engine mount. * The component shows three options for configuration and which form * is shown. The sub-forms rendered handle rendering the form itself * and form submission and cancel actions. */ -export default class PkiConfigureForm extends Component { +export default class PkiConfigureCreate extends Component { @service declare readonly store: Store; @service declare readonly router: Router; @service declare readonly flashMessages: FlashMessageService; + @tracked title = 'Configure PKI'; + get configTypes() { return [ { diff --git a/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.hbs b/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.hbs new file mode 100644 index 000000000..ace909dcf --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.hbs @@ -0,0 +1,17 @@ + + + + + +

+ {{this.title}} +

+
+
+ + \ No newline at end of file diff --git a/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.js b/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.js new file mode 100644 index 000000000..50bdb871b --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-generate-intermediate.js @@ -0,0 +1,6 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +export default class PagePkiIssuerGenerateIntermediateComponent extends Component { + @tracked title = 'Generate intermediate CSR'; +} diff --git a/ui/lib/pki/addon/components/page/pki-issuer-generate-root.hbs b/ui/lib/pki/addon/components/page/pki-issuer-generate-root.hbs new file mode 100644 index 000000000..a12d8b6e5 --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-generate-root.hbs @@ -0,0 +1,18 @@ + + + + + +

+ {{this.title}} +

+
+
+ + \ No newline at end of file diff --git a/ui/lib/pki/addon/components/page/pki-issuer-generate-root.js b/ui/lib/pki/addon/components/page/pki-issuer-generate-root.js new file mode 100644 index 000000000..58bb6de42 --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-generate-root.js @@ -0,0 +1,6 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +export default class PagePkiIssuerGenerateRootComponent extends Component { + @tracked title = 'Generate root'; +} diff --git a/ui/lib/pki/addon/components/page/pki-issuer-import.hbs b/ui/lib/pki/addon/components/page/pki-issuer-import.hbs new file mode 100644 index 000000000..457158987 --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-import.hbs @@ -0,0 +1,18 @@ + + + + + +

+ {{this.title}} +

+
+
+ + \ No newline at end of file diff --git a/ui/lib/pki/addon/components/page/pki-issuer-import.js b/ui/lib/pki/addon/components/page/pki-issuer-import.js new file mode 100644 index 000000000..8a5b7c444 --- /dev/null +++ b/ui/lib/pki/addon/components/page/pki-issuer-import.js @@ -0,0 +1,6 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +export default class PagePkiIssuerImportComponent extends Component { + @tracked title = 'Import a CA'; +} diff --git a/ui/lib/pki/addon/components/pki-configure-form.hbs b/ui/lib/pki/addon/components/pki-configure-form.hbs deleted file mode 100644 index 7e472c961..000000000 --- a/ui/lib/pki/addon/components/pki-configure-form.hbs +++ /dev/null @@ -1,69 +0,0 @@ -
- {{#unless @config.id}} -
- {{#each this.configTypes as |option|}} -
- -
- {{/each}} -
- {{/unless}} - {{#if (eq @config.actionType "import")}} - - {{else if (eq @config.actionType "generate-root")}} - - {{else if (eq @config.actionType "generate-csr")}} - - {{else}} - -
-
- - -
-
- {{/if}} -
\ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-generate-csr.ts b/ui/lib/pki/addon/components/pki-generate-csr.ts index e8fc40a15..b92985264 100644 --- a/ui/lib/pki/addon/components/pki-generate-csr.ts +++ b/ui/lib/pki/addon/components/pki-generate-csr.ts @@ -19,8 +19,28 @@ interface Args { useIssuer: boolean; onComplete: CallableFunction; onCancel: CallableFunction; + onSave?: CallableFunction; } +/** + * @module PkiGenerateCsrComponent + * PkiGenerateCsr shows only the fields valid for the generate CSR endpoint. + * This component renders the form, handles the model save and rollback actions, + * and shows the resulting data on success. onCancel is required for the cancel + * transition, and if onSave is provided it will call that after save for any + * side effects in the parent. + * + * @example + * ```js + * + * ``` + * + * @param {Object} model - pki/action model. + * @callback onCancel - Callback triggered when cancel button is clicked, after model is unloaded + * @callback onSave - Optional - Callback triggered after model is saved, as a side effect. Results are shown on the same component + * @callback onComplete - Callback triggered when "Done" button clicked, on results view + * @param {Object} adapterOptions - object passed as adapterOptions on the model.save method + */ export default class PkiGenerateCsrComponent extends Component { @service declare readonly flashMessages: FlashMessageService; @@ -64,12 +84,15 @@ export default class PkiGenerateCsrComponent extends Component { *save(event: Event): Generator> { event.preventDefault(); try { - const { model } = this.args; + const { model, onSave } = this.args; const { isValid, state, invalidFormMessage } = model.validate(); if (isValid) { const useIssuer = yield this.getCapability(); yield model.save({ adapterOptions: { actionType: 'generate-csr', useIssuer } }); this.flashMessages.success('Successfully generated CSR.'); + if (onSave) { + onSave(); + } } else { this.modelValidations = state; this.alert = invalidFormMessage; diff --git a/ui/lib/pki/addon/components/pki-generate-root.hbs b/ui/lib/pki/addon/components/pki-generate-root.hbs index 5200c691f..be875f0d9 100644 --- a/ui/lib/pki/addon/components/pki-generate-root.hbs +++ b/ui/lib/pki/addon/components/pki-generate-root.hbs @@ -1,69 +1,126 @@ -
- - - {{#each this.defaultFields as |field|}} - {{#let (find-by "name" field @model.allFields) as |attr|}} - - {{#if (eq field "customTtl")}} - {{! customTtl attr has editType yield, which will render this }} - - {{/if}} - - {{/let}} - {{/each}} - - - - {{#if @urls}} -
- - {{#if @urls.canSet}} - {{#each @urls.allFields as |attr|}} - {{#if (not (eq attr.name "mountPath"))}} - - {{/if}} - {{/each}} - {{else}} - - {{/if}} -
- {{/if}} - -
-
- - +{{! Show results if model has an ID, which is only generated after save }} +{{#if @model.id}} + + {{#if @model.privateKey}} +
+
- {{#if this.invalidFormAlert}} + {{/if}} +
+ {{#each this.returnedFields as |field|}} + {{#let (find-by "name" field @model.allFields) as |attr|}} + {{#if attr.options.detailLinkTo}} + + {{get @model attr.name}} + + {{else if attr.options.masked}} + + + + {{else}} + + {{/if}} + {{/let}} + {{/each}} + + {{#if @model.privateKey}} + + {{else}} + internal + {{/if}} + + + {{or @model.privateKeyType "internal"}} + +
+
+
- +
+
+
+{{else}} + + + + {{#each this.defaultFields as |field|}} + {{#let (find-by "name" field @model.allFields) as |attr|}} + + {{#if (eq field "customTtl")}} + {{! customTtl attr has editType yield, which will render this }} + + {{/if}} + + {{/let}} + {{/each}} + + + + {{#if @urls}} +
+ + {{#if @urls.canSet}} + {{#each @urls.allFields as |attr|}} + {{#if (not (eq attr.name "mountPath"))}} + + {{/if}} + {{/each}} + {{else}} + + {{/if}} +
{{/if}} -
- \ No newline at end of file + +
+
+ + +
+ {{#if this.invalidFormAlert}} +
+ +
+ {{/if}} +
+ +{{/if}} \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-generate-root.js b/ui/lib/pki/addon/components/pki-generate-root.ts similarity index 55% rename from ui/lib/pki/addon/components/pki-generate-root.js rename to ui/lib/pki/addon/components/pki-generate-root.ts index 8f6743144..93f12728b 100644 --- a/ui/lib/pki/addon/components/pki-generate-root.js +++ b/ui/lib/pki/addon/components/pki-generate-root.ts @@ -4,34 +4,53 @@ */ import { action } from '@ember/object'; +import RouterService from '@ember/routing/router-service'; import { service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; +import PkiActionModel from 'vault/models/pki/action'; +import PkiUrlsModel from 'vault/models/pki/urls'; +import FlashMessageService from 'vault/services/flash-messages'; import errorMessage from 'vault/utils/error-message'; +interface AdapterOptions { + actionType: string; + useIssuer: boolean | undefined; +} +interface Args { + model: PkiActionModel; + urls: PkiUrlsModel; + onCancel: CallableFunction; + onComplete: CallableFunction; + onSave?: CallableFunction; + adapterOptions: AdapterOptions; +} + /** * @module PkiGenerateRoot * PkiGenerateRoot shows only the fields valid for the generate root endpoint. - * This form handles the model save and rollback actions, and will call the passed - * onSave and onCancel args for transition (passed from parent). - * NOTE: this component is not TS because decorator-added parameters (eg validator and - * formFields) aren't recognized on the model. + * This component renders the form, handles the model save and rollback actions, + * and shows the resulting data on success. onCancel is required for the cancel + * transition, and if onSave is provided it will call that after save for any + * side effects in the parent. * * @example * ```js - * + * * ``` * * @param {Object} model - pki/action model. * @callback onCancel - Callback triggered when cancel button is clicked, after model is unloaded + * @callback onSave - Optional - Callback triggered after model is saved, as a side effect. Results are shown on the same component + * @callback onComplete - Callback triggered when "Done" button clicked, on results view * @param {Object} adapterOptions - object passed as adapterOptions on the model.save method */ -export default class PkiGenerateRootComponent extends Component { - @service flashMessages; - @service router; - @tracked showGroup = null; +export default class PkiGenerateRootComponent extends Component { + @service declare readonly flashMessages: FlashMessageService; + @service declare readonly router: RouterService; + @tracked modelValidations = null; @tracked errorBanner = ''; @tracked invalidFormAlert = ''; @@ -49,6 +68,19 @@ export default class PkiGenerateRootComponent extends Component { ]; } + get returnedFields() { + return [ + 'certificate', + 'expiration', + 'issuerId', + 'issuerName', + 'issuingCa', + 'keyId', + 'keyName', + 'serialNumber', + ]; + } + @action cancel() { // Generate root form will always have a new model this.args.model.unloadRecord(); @@ -68,19 +100,17 @@ export default class PkiGenerateRootComponent extends Component { @task @waitFor - *save(event) { + *save(event: Event) { event.preventDefault(); const continueSave = this.checkFormValidity(); if (!continueSave) return; try { yield this.setUrls(); - const result = yield this.args.model.save({ adapterOptions: this.args.adapterOptions }); + yield this.args.model.save({ adapterOptions: this.args.adapterOptions }); this.flashMessages.success('Successfully generated root.'); - this.router.transitionTo( - 'vault.cluster.secrets.backend.pki.issuers.issuer.details', - result.backend, - result.issuerId - ); + if (this.args.onSave) { + this.args.onSave(); + } } catch (e) { this.errorBanner = errorMessage(e); this.invalidFormAlert = 'There was a problem generating the root.'; diff --git a/ui/lib/pki/addon/components/pki-import-pem-bundle.hbs b/ui/lib/pki/addon/components/pki-import-pem-bundle.hbs index 0fc6a7caa..0b799d97b 100644 --- a/ui/lib/pki/addon/components/pki-import-pem-bundle.hbs +++ b/ui/lib/pki/addon/components/pki-import-pem-bundle.hbs @@ -1,36 +1,87 @@ -
-
- -
- -
- -

- Issuer URLs (Issuing certificates, CRL distribution points, OCSP servers, and delta CRL URLs) can be specified by - editing the individual issuer once it is uploaded to Vault. -

-
-
- - -
- +{{#if this.importedResponse}} + +
+
+

+ Imported Issuer +

+
+
+

+ Imported Key +

+
-
\ No newline at end of file +
+ {{#each-in this.importedResponse as |issuer key|}} +
+
+
+ {{#if issuer}} + + {{issuer}} + + {{else}} + None + {{/if}} +
+
+ {{#if key}} + + {{key}} + + {{else}} + None + {{/if}} +
+
+
+ {{/each-in}} +
+
+
+
+ +
+
+
+{{else}} +
+
+ +
+ +
+ +

+ Issuer URLs (Issuing certificates, CRL distribution points, OCSP servers, and delta CRL URLs) can be specified by + editing the individual issuer once it is uploaded to Vault. +

+
+
+ + +
+ +
+
+{{/if}} \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-import-pem-bundle.ts b/ui/lib/pki/addon/components/pki-import-pem-bundle.ts index b68e8cb68..3549ce314 100644 --- a/ui/lib/pki/addon/components/pki-import-pem-bundle.ts +++ b/ui/lib/pki/addon/components/pki-import-pem-bundle.ts @@ -33,8 +33,9 @@ interface AdapterOptions { useIssuer: boolean | undefined; } interface Args { - onSave: CallableFunction; + onSave?: CallableFunction; onCancel: CallableFunction; + onComplete: CallableFunction; model: PkiActionModel; adapterOptions: AdapterOptions; } @@ -44,14 +45,28 @@ export default class PkiImportPemBundle extends Component { @tracked errorBanner = ''; + get importedResponse() { + // mapping only exists after success + // TODO VAULT-14791: handle issuer already exists, but key doesn't -- empty object returned here + return this.args.model.mapping; + } + @task @waitFor *submitForm(event: Event) { event.preventDefault(); + this.errorBanner = ''; + if (!this.args.model.pemBundle) { + this.errorBanner = 'please upload your PEM bundle'; + return; + } try { yield this.args.model.save({ adapterOptions: this.args.adapterOptions }); this.flashMessages.success('Successfully imported data.'); - this.args.onSave(); + // This component shows the results, but call `onSave` for any side effects on parent + if (this.args.onSave) { + this.args.onSave(); + } } catch (error) { this.errorBanner = errorMessage(error); } diff --git a/ui/lib/pki/addon/templates/configuration/create.hbs b/ui/lib/pki/addon/templates/configuration/create.hbs index 06fe28945..2c180aa03 100644 --- a/ui/lib/pki/addon/templates/configuration/create.hbs +++ b/ui/lib/pki/addon/templates/configuration/create.hbs @@ -1,15 +1,5 @@ - - - - - -

- Configure PKI -

-
-
- - - - - - -

- Generate intermediate CSR -

-
- - - \ No newline at end of file + \ No newline at end of file diff --git a/ui/lib/pki/addon/templates/issuers/generate-root.hbs b/ui/lib/pki/addon/templates/issuers/generate-root.hbs index 5ab0bfaac..574b54e39 100644 --- a/ui/lib/pki/addon/templates/issuers/generate-root.hbs +++ b/ui/lib/pki/addon/templates/issuers/generate-root.hbs @@ -1,16 +1 @@ - - - - - -

- Generate root -

-
-
- - \ No newline at end of file + \ No newline at end of file diff --git a/ui/lib/pki/addon/templates/issuers/import.hbs b/ui/lib/pki/addon/templates/issuers/import.hbs index 4902c32b9..695e9e621 100644 --- a/ui/lib/pki/addon/templates/issuers/import.hbs +++ b/ui/lib/pki/addon/templates/issuers/import.hbs @@ -1,16 +1 @@ - - - - - -

- Import a CA -

-
-
- \ No newline at end of file + \ No newline at end of file diff --git a/ui/tests/acceptance/pki/pki-action-forms-test.js b/ui/tests/acceptance/pki/pki-action-forms-test.js new file mode 100644 index 000000000..409c87bcb --- /dev/null +++ b/ui/tests/acceptance/pki/pki-action-forms-test.js @@ -0,0 +1,274 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, skip, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; +import { click, currentURL, fillIn, typeIn, visit } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { v4 as uuidv4 } from 'uuid'; + +import authPage from 'vault/tests/pages/auth'; +import logout from 'vault/tests/pages/logout'; +import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; +import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands'; +import { SELECTORS as S } from 'vault/tests/helpers/pki/workflow'; +import { issuerPemBundle } from 'vault/tests/helpers/pki/values'; + +module('Acceptance | pki action forms test', function (hooks) { + setupApplicationTest(hooks); + + hooks.beforeEach(async function () { + await authPage.login(); + // Setup PKI engine + const mountPath = `pki-workflow-${uuidv4()}`; + await enablePage.enable('pki', mountPath); + this.mountPath = mountPath; + await logout.visit(); + }); + + hooks.afterEach(async function () { + await logout.visit(); + await authPage.login(); + // Cleanup engine + await runCommands([`delete sys/mounts/${this.mountPath}`]); + await logout.visit(); + }); + + module('import', function (hooks) { + setupMirage(hooks); + + hooks.beforeEach(function () { + this.pemBundle = issuerPemBundle; + }); + + test('happy path', async function (assert) { + await authPage.login(this.pkiAdminToken); + await visit(`/vault/secrets/${this.mountPath}/pki/overview`); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); + await click(S.emptyStateLink); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`); + assert.dom(S.configuration.title).hasText('Configure PKI'); + assert.dom(S.configuration.emptyState).exists({ count: 1 }, 'Shows empty state by default'); + await click(S.configuration.optionByKey('import')); + assert.dom(S.configuration.emptyState).doesNotExist(); + // Submit before filling out form shows an error + await click('[data-test-pki-import-pem-bundle]'); + assert.dom('[data-test-alert-banner="alert"]').hasText('Error please upload your PEM bundle'); + // Fill in form data + await click('[data-test-text-toggle]'); + await fillIn('[data-test-text-file-textarea]', this.pemBundle); + await click('[data-test-pki-import-pem-bundle]'); + + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/configuration/create`, + 'stays on page on success' + ); + assert.dom(S.configuration.title).hasText('View imported items'); + assert.dom(S.configuration.importForm).doesNotExist('import form is hidden after save'); + assert.dom(S.configuration.importMapping).exists('import mapping is shown after save'); + await click('[data-test-done]'); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/overview`, + 'redirects to overview when done' + ); + }); + skip('with many imports', async function (assert) { + // TODO VAULT-14791 + this.server.post(`${this.mountPath}/config/ca`, () => { + return { + request_id: 'some-config-id', + data: { + imported_issuers: ['my-imported-issuer', 'imported2'], + imported_keys: ['my-imported-key', 'imported3'], + mapping: { + 'my-imported-issuer': 'my-imported-key', + imported2: '', + }, + }, + }; + }); + await authPage.login(this.pkiAdminToken); + await visit(`/vault/secrets/${this.mountPath}/pki/configuration/create`); + await click(S.configuration.optionByKey('import')); + await click('[data-test-text-toggle]'); + await fillIn('[data-test-text-file-textarea]', this.pemBundle); + await click('[data-test-pki-import-pem-bundle]'); + + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/configuration/create`, + 'stays on page on success' + ); + assert.dom(S.configuration.title).hasText('View imported items'); + assert.dom(S.configuration.importForm).doesNotExist('import form is hidden after save'); + assert.dom(S.configuration.importMapping).exists('import mapping is shown after save'); + assert.dom(S.configuration.importedIssuer).hasText('my-imported-issuer', 'Issuer value is displayed'); + assert.dom(S.configuration.importedKey).hasText('my-imported-key', 'Key value is displayed'); + await click('[data-test-done]'); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/overview`, + 'redirects to overview when done' + ); + }); + skip('shows imported items when keys is empty', async function (assert) { + // TODO VAULT-14791 + this.server.post(`${this.mountPath}/config/ca`, () => { + return { + request_id: 'some-config-id', + data: { + imported_issuers: ['my-imported-issuer', 'my-imported-issuer2'], + imported_keys: null, + mapping: { + 'my-imported-issuer': '', + 'my-imported-issuer2': '', + }, + }, + }; + }); + await authPage.login(this.pkiAdminToken); + await visit(`/vault/secrets/${this.mountPath}/pki/configuration/create`); + await click(S.configuration.optionByKey('import')); + await click('[data-test-text-toggle]'); + await fillIn('[data-test-text-file-textarea]', this.pemBundle); + await click('[data-test-pki-import-pem-bundle]'); + + assert.dom(S.configuration.importForm).doesNotExist('import form is hidden after save'); + assert.dom(S.configuration.importMapping).exists('import mapping is shown after save'); + assert.dom(S.configuration.importedIssuer).hasText('my-imported-issuer', 'Issuer value is displayed'); + assert.dom(S.configuration.importedKey).hasText('my-imported-key', 'Key value is displayed'); + }); + }); + + module('generate root', function () { + test('happy path', async function (assert) { + const commonName = 'my-common-name'; + const issuerName = 'my-first-issuer'; + const keyName = 'my-first-key'; + await authPage.login(); + await visit(`/vault/secrets/${this.mountPath}/pki/overview`); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); + await click(S.emptyStateLink); + assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`); + assert.dom(S.configuration.title).hasText('Configure PKI'); + assert.dom(S.configuration.emptyState).exists({ count: 1 }, 'Shows empty state by default'); + await click(S.configuration.optionByKey('generate-root')); + assert.dom(S.configuration.emptyState).doesNotExist(); + // The URLs section is populated based on params returned from OpenAPI. This test will break when + // the backend adds fields. We should update the count accordingly. + assert.dom(S.configuration.urlField).exists({ count: 4 }); + // Fill in form + await fillIn(S.configuration.typeField, 'internal'); + await typeIn(S.configuration.inputByName('commonName'), commonName); + await typeIn(S.configuration.inputByName('issuerName'), issuerName); + await click(S.configuration.keyParamsGroupToggle); + await typeIn(S.configuration.inputByName('keyName'), keyName); + await click(S.configuration.generateRootSave); + + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/configuration/create`, + 'stays on page on success' + ); + assert.dom(S.configuration.title).hasText('View root certificate'); + assert.dom('[data-test-alert-banner="alert"]').doesNotExist('no private key warning'); + assert.dom(S.configuration.title).hasText('View root certificate', 'Updates title on page'); + assert.dom(S.configuration.saved.certificate).hasClass('allow-copy', 'copyable certificate is masked'); + assert.dom(S.configuration.saved.issuerName).hasText(issuerName); + assert.dom(S.configuration.saved.issuerLink).exists('Issuer link exists'); + assert.dom(S.configuration.saved.keyLink).exists('Key link exists'); + assert.dom(S.configuration.saved.keyName).hasText(keyName); + assert.dom('[data-test-done]').exists('Done button exists'); + // Check that linked issuer has correct common name + await click(S.configuration.saved.issuerLink); + assert.dom(S.issuerDetails.valueByName('Common name')).hasText(commonName); + }); + test('type=exported', async function (assert) { + const commonName = 'my-exported-name'; + await authPage.login(); + await visit(`/vault/secrets/${this.mountPath}/pki/configuration/create`); + await click(S.configuration.optionByKey('generate-root')); + // Fill in form + await fillIn(S.configuration.typeField, 'exported'); + await typeIn(S.configuration.inputByName('commonName'), commonName); + await click(S.configuration.generateRootSave); + + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/configuration/create`, + 'stays on page on success' + ); + assert.dom(S.configuration.title).hasText('View root certificate'); + assert + .dom('[data-test-alert-banner="alert"]') + .hasText('Next steps The private_key is only available once. Make sure you copy and save it now.'); + assert.dom(S.configuration.title).hasText('View root certificate', 'Updates title on page'); + assert + .dom(S.configuration.saved.certificate) + .hasClass('allow-copy', 'copyable masked certificate exists'); + assert + .dom(S.configuration.saved.issuerName) + .doesNotExist('Issuer name not shown because it was not named'); + assert.dom(S.configuration.saved.issuerLink).exists('Issuer link exists'); + assert.dom(S.configuration.saved.keyLink).exists('Key link exists'); + assert + .dom(S.configuration.saved.privateKey) + .hasClass('allow-copy', 'copyable masked private key exists'); + assert.dom(S.configuration.saved.keyName).doesNotExist('Key name not shown because it was not named'); + assert.dom('[data-test-done]').exists('Done button exists'); + // Check that linked issuer has correct common name + await click(S.configuration.saved.issuerLink); + assert.dom(S.issuerDetails.valueByName('Common name')).hasText(commonName); + }); + }); + + module('generate CSR', function () { + test('happy path', async function (assert) { + await authPage.login(); + await visit(`/vault/secrets/${this.mountPath}/pki/overview`); + await click(S.emptyStateLink); + assert.dom(S.configuration.title).hasText('Configure PKI'); + await click(S.configuration.optionByKey('generate-csr')); + await fillIn(S.configuration.typeField, 'internal'); + await fillIn(S.configuration.inputByName('commonName'), 'my-common-name'); + await click('[data-test-save]'); + assert.dom(S.configuration.title).hasText('View generated CSR'); + await assert.dom(S.configuration.csrDetails).exists('renders CSR details after save'); + await click('[data-test-done]'); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/overview`, + 'Transitions to overview after viewing csr details' + ); + }); + test('type = exported', async function (assert) { + await authPage.login(); + await visit(`/vault/secrets/${this.mountPath}/pki/overview`); + await click(S.emptyStateLink); + await click(S.configuration.optionByKey('generate-csr')); + await fillIn(S.configuration.typeField, 'exported'); + await fillIn(S.configuration.inputByName('commonName'), 'my-common-name'); + await click('[data-test-save]'); + await assert.dom(S.configuration.csrDetails).exists('renders CSR details after save'); + assert.dom(S.configuration.title).hasText('View generated CSR'); + assert + .dom('[data-test-alert-banner="alert"]') + .hasText( + 'Next steps Copy the CSR below for a parent issuer to sign and then import the signed certificate back into this mount. The private_key is only available once. Make sure you copy and save it now.' + ); + assert + .dom(S.configuration.saved.privateKey) + .hasClass('allow-copy', 'copyable masked private key exists'); + await click('[data-test-done]'); + assert.strictEqual( + currentURL(), + `/vault/secrets/${this.mountPath}/pki/overview`, + 'Transitions to overview after viewing csr details' + ); + }); + }); +}); diff --git a/ui/tests/acceptance/pki-engine-route-cleanupt-test.js b/ui/tests/acceptance/pki/pki-engine-route-cleanup-test.js similarity index 98% rename from ui/tests/acceptance/pki-engine-route-cleanupt-test.js rename to ui/tests/acceptance/pki/pki-engine-route-cleanup-test.js index 291aae7d9..1069b82ba 100644 --- a/ui/tests/acceptance/pki-engine-route-cleanupt-test.js +++ b/ui/tests/acceptance/pki/pki-engine-route-cleanup-test.js @@ -12,7 +12,7 @@ import logout from 'vault/tests/pages/logout'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; import { click, currentURL, fillIn, visit } from '@ember/test-helpers'; import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands'; -import { SELECTORS } from '../helpers/pki/workflow'; +import { SELECTORS } from 'vault/tests/helpers/pki/workflow'; /** * This test module should test that dirty route models are cleaned up when the user leaves the page @@ -295,14 +295,12 @@ module('Acceptance | pki engine route cleanup test', function (hooks) { await fillIn(SELECTORS.configuration.typeField, 'internal'); await fillIn(SELECTORS.configuration.inputByName('commonName'), 'my-root-cert'); await click(SELECTORS.configuration.generateRootSave); + // Go to list view so we fetch all the issuers + await visit(`/vault/secrets/${this.mountPath}/pki/issuers`); issuers = this.store.peekAll('pki/issuer'); const issuerId = issuers.objectAt(0).id; assert.strictEqual(issuers.length, 1, 'Issuer exists on model'); - assert.strictEqual( - currentURL(), - `/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/details`, - 'url is correct' - ); + await visit(`/vault/secrets/${this.mountPath}/pki/issuers/${issuerId}/details`); await click(SELECTORS.issuerDetails.configure); issuer = this.store.peekRecord('pki/issuer', issuerId); assert.false(issuer.hasDirtyAttributes, 'Model not dirty'); diff --git a/ui/tests/acceptance/pki/pki-engine-workflow-test.js b/ui/tests/acceptance/pki/pki-engine-workflow-test.js index 15254437e..4a99fab78 100644 --- a/ui/tests/acceptance/pki/pki-engine-workflow-test.js +++ b/ui/tests/acceptance/pki/pki-engine-workflow-test.js @@ -14,7 +14,6 @@ import { click, currentURL, fillIn, find, isSettled, visit } from '@ember/test-h import { SELECTORS } from 'vault/tests/helpers/pki/workflow'; import { adminPolicy, readerPolicy, updatePolicy } from 'vault/tests/helpers/policy-generator/pki'; import { tokenWithPolicy, runCommands } from 'vault/tests/helpers/pki/pki-run-commands'; -import { rootPem } from 'vault/tests/helpers/pki/values'; /** * This test module should test the PKI workflow, including: @@ -76,74 +75,6 @@ module('Acceptance | pki workflow', function (hooks) { assertEmptyState(assert, 'keys'); }); - module('configuration', function (hooks) { - hooks.beforeEach(function () { - this.pemBundle = rootPem; - }); - test('import happy path', async function (assert) { - await authPage.login(this.pkiAdminToken); - await visit(`/vault/secrets/${this.mountPath}/pki/overview`); - assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); - await click(SELECTORS.emptyStateLink); - assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`); - assert.dom(SELECTORS.configuration.title).hasText('Configure PKI'); - assert.dom(SELECTORS.configuration.emptyState).exists({ count: 1 }, 'Shows empty state by default'); - await click(SELECTORS.configuration.optionByKey('import')); - assert.dom(SELECTORS.configuration.emptyState).doesNotExist(); - await click('[data-test-text-toggle]'); - await fillIn('[data-test-text-file-textarea]', this.pemBundle); - await click('[data-test-pki-import-pem-bundle]'); - assert.strictEqual( - currentURL(), - `/vault/secrets/${this.mountPath}/pki/issuers`, - 'redirects to issuers list on success' - ); - }); - - test('generate-root happy path', async function (assert) { - await authPage.login(this.pkiAdminToken); - await visit(`/vault/secrets/${this.mountPath}/pki/overview`); - assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`); - await click(SELECTORS.emptyStateLink); - assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`); - assert.dom(SELECTORS.configuration.title).hasText('Configure PKI'); - assert.dom(SELECTORS.configuration.emptyState).exists({ count: 1 }, 'Shows empty state by default'); - await click(SELECTORS.configuration.optionByKey('generate-root')); - assert.dom(SELECTORS.configuration.emptyState).doesNotExist(); - // The URLs section is populated based on params returned from OpenAPI. This test will break when - // the backend adds fields. We should update the count accordingly. - assert.dom(SELECTORS.configuration.urlField).exists({ count: 4 }); - // Fill in form - await fillIn(SELECTORS.configuration.typeField, 'exported'); - await fillIn(SELECTORS.configuration.inputByName('commonName'), 'my-common-name'); - await fillIn(SELECTORS.configuration.inputByName('issuerName'), 'my-first-issuer'); - await click(SELECTORS.configuration.generateRootSave); - - assert - .dom(SELECTORS.issuerDetails.title) - .hasText('View issuer certificate', 'Redirects to view issuer page'); - assert.dom(SELECTORS.issuerDetails.valueByName('Common name')).hasText('my-common-name'); - assert.dom(SELECTORS.issuerDetails.valueByName('Issuer name')).hasText('my-first-issuer'); - }); - - test('it should generate intermediate csr', async function (assert) { - await authPage.login(this.pkiAdminToken); - await visit(`/vault/secrets/${this.mountPath}/pki/overview`); - await click(SELECTORS.emptyStateLink); - await click(SELECTORS.configuration.optionByKey('generate-csr')); - await fillIn(SELECTORS.configuration.typeField, 'exported'); - await fillIn(SELECTORS.configuration.inputByName('commonName'), 'my-common-name'); - await click('[data-test-save]'); - await assert.dom(SELECTORS.configuration.csrDetails).exists('renders CSR details after save'); - await click('[data-test-done]'); - assert.strictEqual( - currentURL(), - `/vault/secrets/${this.mountPath}/pki/issuers`, - 'Transitions to issuers after viewing csr details' - ); - }); - }); - module('roles', function (hooks) { hooks.beforeEach(async function () { await authPage.login(); @@ -334,7 +265,6 @@ module('Acceptance | pki workflow', function (hooks) { `/vault/secrets/${this.mountPath}/pki/keys/${keyId}/details`, 'navigates to details after save' ); - await this.pauseTest; assert.dom(SELECTORS.keyPages.keyNameValue).hasText('test-key', 'updates key name'); // key generate and delete navigation diff --git a/ui/tests/helpers/pki/pki-configure-form.js b/ui/tests/helpers/pki/pki-configure-create.js similarity index 63% rename from ui/tests/helpers/pki/pki-configure-form.js rename to ui/tests/helpers/pki/pki-configure-create.js index ccdfdfe4a..c27bc0a79 100644 --- a/ui/tests/helpers/pki/pki-configure-form.js +++ b/ui/tests/helpers/pki/pki-configure-create.js @@ -6,7 +6,9 @@ import { SELECTORS as GENERATE_ROOT } from './pki-generate-root'; export const SELECTORS = { - // pki-configure-form + // page::pki-configure-create + breadcrumbContainer: '[data-test-breadcrumbs]', + title: '[data-test-pki-engine-page-title]', option: '[data-test-pki-config-option]', optionByKey: (key) => `[data-test-pki-config-option="${key}"]`, cancelButton: '[data-test-pki-config-cancel]', @@ -15,6 +17,10 @@ export const SELECTORS = { ...GENERATE_ROOT, // pki-ca-cert-import importForm: '[data-test-pki-import-pem-bundle-form]', + importSectionLabel: '[data-test-import-section-label]', + importMapping: '[data-test-imported-bundle-mapping]', + importedIssuer: '[data-test-imported-issuer]', + importedKey: '[data-test-imported-key]', // generate-intermediate csrDetails: '[data-test-generate-csr-result]', }; diff --git a/ui/tests/helpers/pki/pki-generate-root.js b/ui/tests/helpers/pki/pki-generate-root.js index fd15d27ac..1344f7eaf 100644 --- a/ui/tests/helpers/pki/pki-generate-root.js +++ b/ui/tests/helpers/pki/pki-generate-root.js @@ -19,4 +19,15 @@ export const SELECTORS = { formInvalidError: '[data-test-pki-generate-root-validation-error]', urlsSection: '[data-test-urls-section]', urlField: '[data-test-urls-section] [data-test-input]', + // Shown values after save + saved: { + certificate: '[data-test-value-div="Certificate"] [data-test-masked-input]', + commonName: '[data-test-row-value="Common name"]', + issuerName: '[data-test-row-value="Issuer name"]', + issuerLink: '[data-test-value-div="Issuer ID"] a', + keyName: '[data-test-row-value="Key name"]', + keyLink: '[data-test-value-div="Key ID"] a', + privateKey: '[data-test-value-div="Private key"] [data-test-masked-input]', + serialNumber: '[data-test-row-value="Serial number"]', + }, }; diff --git a/ui/tests/helpers/pki/workflow.js b/ui/tests/helpers/pki/workflow.js index aa9ba85e4..80863096a 100644 --- a/ui/tests/helpers/pki/workflow.js +++ b/ui/tests/helpers/pki/workflow.js @@ -8,7 +8,7 @@ import { SELECTORS as GENERATECERT } from './pki-role-generate'; import { SELECTORS as KEYFORM } from './pki-key-form'; import { SELECTORS as KEYPAGES } from './page/pki-keys'; import { SELECTORS as ISSUERDETAILS } from './pki-issuer-details'; -import { SELECTORS as CONFIGURATION } from './pki-configure-form'; +import { SELECTORS as CONFIGURATION } from './pki-configure-create'; export const SELECTORS = { breadcrumbContainer: '[data-test-breadcrumbs]', diff --git a/ui/tests/integration/components/pki/pki-configure-form-test.js b/ui/tests/integration/components/pki/page/pki-configure-create-test.js similarity index 50% rename from ui/tests/integration/components/pki/pki-configure-form-test.js rename to ui/tests/integration/components/pki/page/pki-configure-create-test.js index 8f0808695..0757ebe8f 100644 --- a/ui/tests/integration/components/pki/pki-configure-form-test.js +++ b/ui/tests/integration/components/pki/page/pki-configure-create-test.js @@ -8,21 +8,40 @@ import { setupRenderingTest } from 'vault/tests/helpers'; import { click, render } from '@ember/test-helpers'; import { setupEngine } from 'ember-engines/test-support'; import { hbs } from 'ember-cli-htmlbars'; -import { SELECTORS } from 'vault/tests/helpers/pki/pki-configure-form'; +import { SELECTORS } from 'vault/tests/helpers/pki/pki-configure-create'; import sinon from 'sinon'; -module('Integration | Component | pki-configure-form', function (hooks) { +module('Integration | Component | page/pki-configure-create', function (hooks) { setupRenderingTest(hooks); setupEngine(hooks, 'pki'); + hooks.beforeEach(function () { + this.context = { owner: this.engine }; // this.engine set by setupEngine + this.store = this.owner.lookup('service:store'); this.cancelSpy = sinon.spy(); + this.breadcrumbs = [ + { label: 'secrets', route: 'secrets', linkExternal: true }, + { label: 'pki', route: 'overview' }, + { label: 'configure' }, + ]; + this.config = this.store.createRecord('pki/action'); + this.urls = this.store.createRecord('pki/urls'); }); test('it renders', async function (assert) { - await render(hbs``, { - owner: this.engine, - }); - + await render( + hbs` + + `, + this.context + ); + assert.dom(SELECTORS.breadcrumbContainer).exists('breadcrumbs exist'); + assert.dom(SELECTORS.title).hasText('Configure PKI'); assert.dom(SELECTORS.option).exists({ count: 3 }, 'Three configuration options are shown'); assert.dom(SELECTORS.cancelButton).exists('Cancel link is shown'); assert.dom(SELECTORS.saveButton).isDisabled('Done button is disabled'); @@ -32,5 +51,11 @@ module('Integration | Component | pki-configure-form', function (hooks) { await click(SELECTORS.optionByKey('generate-csr')); assert.dom(SELECTORS.optionByKey('generate-csr')).isChecked('Selected item is checked'); + + await click(SELECTORS.optionByKey('generate-root')); + assert.dom(SELECTORS.optionByKey('generate-root')).isChecked('Selected item is checked'); + + await click(SELECTORS.generateRootCancel); + assert.ok(this.cancelSpy.calledOnce); }); }); diff --git a/ui/tests/integration/components/pki/page/pki-issuer-generate-intermediate-test.js b/ui/tests/integration/components/pki/page/pki-issuer-generate-intermediate-test.js new file mode 100644 index 000000000..04616593e --- /dev/null +++ b/ui/tests/integration/components/pki/page/pki-issuer-generate-intermediate-test.js @@ -0,0 +1,80 @@ +import { module, test } from 'qunit'; +import { click, fillIn, render } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupEngine } from 'ember-engines/test-support'; +import { Response } from 'miragejs'; +import { v4 as uuidv4 } from 'uuid'; + +import { setupRenderingTest } from 'vault/tests/helpers'; +import { SELECTORS } from 'vault/tests/helpers/pki/pki-configure-create'; +import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; + +/** + * this test is for the page component only. A separate test is written for the form rendered + */ +module('Integration | Component | page/pki-issuer-generate-intermediate', function (hooks) { + setupRenderingTest(hooks); + setupEngine(hooks, 'pki'); + setupMirage(hooks); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + this.breadcrumbs = [{ label: 'something' }]; + this.model = this.store.createRecord('pki/action', { + actionType: 'generate-csr', + }); + this.secretMountPath = this.owner.lookup('service:secret-mount-path'); + this.secretMountPath.currentPath = 'pki-component'; + this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub()); + }); + + test('it renders correct title before and after submit', async function (assert) { + assert.expect(3); + this.server.post(`/pki-component/issuers/generate/intermediate/internal`, () => { + assert.true(true, 'Issuers endpoint called'); + return { + request_id: uuidv4(), + data: { + csr: '------BEGIN CERTIFICATE------', + key_id: 'some-key-id', + }, + }; + }); + + await render( + hbs``, + { + owner: this.engine, + } + ); + assert.dom('[data-test-pki-page-title]').hasText('Generate intermediate CSR'); + await fillIn(SELECTORS.typeField, 'internal'); + await fillIn(SELECTORS.inputByName('commonName'), 'foobar'); + await click('[data-test-save]'); + assert.dom('[data-test-pki-page-title]').hasText('View generated CSR'); + }); + + test('it does not update title if API response is an error', async function (assert) { + assert.expect(2); + this.server.post( + '/pki-component/issuers/generate/intermediate/internal', + () => new Response(403, {}, { errors: ['API returns this error'] }) + ); + + await render( + hbs``, + { + owner: this.engine, + } + ); + assert.dom('[data-test-pki-page-title]').hasText('Generate intermediate CSR'); + // Fill in + await fillIn(SELECTORS.typeField, 'internal'); + await fillIn(SELECTORS.inputByName('commonName'), 'foobar'); + await click('[data-test-save]'); + assert + .dom('[data-test-pki-page-title]') + .hasText('Generate intermediate CSR', 'title does not change if response is unsuccessful'); + }); +}); diff --git a/ui/tests/integration/components/pki/page/pki-issuer-generate-root-test.js b/ui/tests/integration/components/pki/page/pki-issuer-generate-root-test.js new file mode 100644 index 000000000..a23a47176 --- /dev/null +++ b/ui/tests/integration/components/pki/page/pki-issuer-generate-root-test.js @@ -0,0 +1,77 @@ +import { module, test } from 'qunit'; +import { click, fillIn, render } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupEngine } from 'ember-engines/test-support'; +import { Response } from 'miragejs'; +import { v4 as uuidv4 } from 'uuid'; + +import { setupRenderingTest } from 'vault/tests/helpers'; +import { SELECTORS } from 'vault/tests/helpers/pki/pki-configure-create'; +import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; + +/** + * this test is for the page component only. A separate test is written for the form rendered + */ +module('Integration | Component | page/pki-issuer-generate-root', function (hooks) { + setupRenderingTest(hooks); + setupEngine(hooks, 'pki'); + setupMirage(hooks); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + this.breadcrumbs = [{ label: 'something' }]; + this.model = this.store.createRecord('pki/action', { + actionType: 'generate-csr', + }); + this.secretMountPath = this.owner.lookup('service:secret-mount-path'); + this.secretMountPath.currentPath = 'pki-component'; + this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub()); + }); + + test('it renders correct title before and after submit', async function (assert) { + assert.expect(3); + this.server.post(`/pki-component/root/generate/internal`, () => { + assert.true(true, 'Root endpoint called'); + return { + request_id: uuidv4(), + data: { + certificate: '------BEGIN CERTIFICATE------', + key_id: 'some-key-id', + }, + }; + }); + + await render( + hbs``, + { + owner: this.engine, + } + ); + assert.dom('[data-test-pki-page-title]').hasText('Generate root'); + await fillIn(SELECTORS.typeField, 'internal'); + await fillIn(SELECTORS.inputByName('commonName'), 'foobar'); + await click(SELECTORS.generateRootSave); + assert.dom('[data-test-pki-page-title]').hasText('View generated root'); + }); + + test('it does not update title if API response is an error', async function (assert) { + assert.expect(2); + this.server.post(`/pki-component/root/generate/internal`, () => new Response(404, {}, { errors: [] })); + + await render( + hbs``, + { + owner: this.engine, + } + ); + assert.dom('[data-test-pki-page-title]').hasText('Generate root'); + // Fill in + await fillIn(SELECTORS.typeField, 'internal'); + await fillIn(SELECTORS.inputByName('commonName'), 'foobar'); + await click(SELECTORS.generateRootSave); + assert + .dom('[data-test-pki-page-title]') + .hasText('Generate root', 'title does not change if response is unsuccessful'); + }); +}); diff --git a/ui/tests/integration/components/pki/page/pki-issuer-import-test.js b/ui/tests/integration/components/pki/page/pki-issuer-import-test.js new file mode 100644 index 000000000..9cc3ecf68 --- /dev/null +++ b/ui/tests/integration/components/pki/page/pki-issuer-import-test.js @@ -0,0 +1,67 @@ +import { module, test } from 'qunit'; +import { click, fillIn, render } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupEngine } from 'ember-engines/test-support'; +import { Response } from 'miragejs'; +import { v4 as uuidv4 } from 'uuid'; +import { setupRenderingTest } from 'vault/tests/helpers'; +import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs'; + +/** + * this test is for the page component only. A separate test is written for the form rendered + */ +module('Integration | Component | page/pki-issuer-import', function (hooks) { + setupRenderingTest(hooks); + setupEngine(hooks, 'pki'); + setupMirage(hooks); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + this.breadcrumbs = [{ label: 'something' }]; + this.model = this.store.createRecord('pki/action', { + actionType: 'generate-csr', + }); + this.secretMountPath = this.owner.lookup('service:secret-mount-path'); + this.secretMountPath.currentPath = 'pki-component'; + this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub()); + }); + + test('it renders correct title before and after submit', async function (assert) { + assert.expect(3); + this.server.post(`/pki-component/issuers/import/bundle`, () => { + assert.true(true, 'Import endpoint called'); + return { + request_id: uuidv4(), + data: {}, + }; + }); + + await render(hbs``, { + owner: this.engine, + }); + assert.dom('[data-test-pki-page-title]').hasText('Import a CA'); + await click('[data-test-text-toggle]'); + await fillIn('[data-test-text-file-textarea]', 'dummy-pem-bundle'); + await click('[data-test-pki-import-pem-bundle]'); + assert.dom('[data-test-pki-page-title]').hasText('View imported items'); + }); + + test('it does not update title if API response is an error', async function (assert) { + assert.expect(2); + // this.server.post('/pki-component/issuers/import/bundle', () => new Response(404, {}, { errors: ['Some error occurred'] })); + this.server.post(`/pki-component/issuers/import/bundle`, () => new Response(404, {}, { errors: [] })); + + await render(hbs``, { + owner: this.engine, + }); + assert.dom('[data-test-pki-page-title]').hasText('Import a CA'); + // Fill in + await click('[data-test-text-toggle]'); + await fillIn('[data-test-text-file-textarea]', 'dummy-pem-bundle'); + await click('[data-test-pki-import-pem-bundle]'); + assert + .dom('[data-test-pki-page-title]') + .hasText('Import a CA', 'title does not change if response is unsuccessful'); + }); +}); diff --git a/ui/types/vault/app-types.ts b/ui/types/vault/app-types.ts index e8eb28330..daadd20ea 100644 --- a/ui/types/vault/app-types.ts +++ b/ui/types/vault/app-types.ts @@ -28,3 +28,9 @@ export interface ModelValidation { }; invalidFormMessage: string; } + +export interface Breadcrumb { + label: string; + route?: string; + linkExternal?: boolean; +} diff --git a/ui/types/vault/models/pki/action.d.ts b/ui/types/vault/models/pki/action.d.ts index 69114943d..e1af7bab2 100644 --- a/ui/types/vault/models/pki/action.d.ts +++ b/ui/types/vault/models/pki/action.d.ts @@ -9,9 +9,42 @@ import CapabilitiesModel from '../capabilities'; export default class PkiActionModel extends Model { secretMountPath: unknown; - pemBundle: string; - type: string; actionType: string | null; + pemBundle: string; + importedIssuers: unknown; + importedKeys: unknown; + mapping: unknown; + type: string; + issuerName: string; + keyName: string; + keyRef: string; + commonName: string; + altNames: string[]; + ipSans: string[]; + uriSans: string[]; + otherSans: string[]; + format: string; + privateKeyFormat: string; + keyType: string; + keyBits: string; + maxPathLength: number; + excludeCnFromSans: boolean; + permittedDnsDomains: string; + ou: string[]; + serialNumber: string; + addBasicConstraints: boolean; + notBeforeDuration: string; + managedKeyName: string; + managedKeyId: string; + customTtl: string; + ttl: string; + notAfter: string; + issuerId: string; + csr: string; + caChain: string; + keyId: string; + privateKey: string; + privateKeyType: string; get backend(): string; // apiPaths for capabilities importBundlePath: Promise;