UI: standardize display for type=exported (#19672)
This commit is contained in:
parent
db31cf2da2
commit
55d18515c1
|
@ -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;
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-engine-page-title>
|
||||
{{this.title}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
{{#unless @config.id}}
|
||||
<div class="box is-bottomless is-fullwidth is-marginless">
|
||||
<div class="columns">
|
||||
{{#each this.configTypes as |option|}}
|
||||
<div class="column is-flex">
|
||||
<label for={{option.key}} class="box-label is-column {{if (eq @config.actionType option.key) 'is-selected'}}">
|
||||
<div>
|
||||
<h3 class="box-label-header title is-6">
|
||||
<Icon @size="24" @name={{option.icon}} />
|
||||
{{option.label}}
|
||||
</h3>
|
||||
<p class="help has-text-grey-dark">
|
||||
{{option.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<RadioButton
|
||||
id={{option.key}}
|
||||
name="pki-config-type"
|
||||
@value={{option.key}}
|
||||
@groupValue={{@config.actionType}}
|
||||
@onChange={{fn (mut @config.actionType) option.key}}
|
||||
data-test-pki-config-option={{option.key}}
|
||||
/>
|
||||
<label for={{option.key}}></label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{#if (eq @config.actionType "import")}}
|
||||
<PkiImportPemBundle
|
||||
@model={{@config}}
|
||||
@onCancel={{@onCancel}}
|
||||
@onSave={{fn (mut this.title) "View imported items"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
@adapterOptions={{hash actionType=@config.actionType useIssuer=@config.canImportBundle}}
|
||||
/>
|
||||
{{else if (eq @config.actionType "generate-root")}}
|
||||
<PkiGenerateRoot
|
||||
@model={{@config}}
|
||||
@urls={{@urls}}
|
||||
@onCancel={{@onCancel}}
|
||||
@adapterOptions={{hash actionType=@config.actionType useIssuer=@config.canGenerateIssuerRoot}}
|
||||
@onSave={{fn (mut this.title) "View root certificate"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
/>
|
||||
{{else if (eq @config.actionType "generate-csr")}}
|
||||
<PkiGenerateCsr
|
||||
@model={{@config}}
|
||||
@onCancel={{@onCancel}}
|
||||
@onSave={{fn (mut this.title) "View generated CSR"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
/>
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="Choose an option"
|
||||
@message="To see configuration options, choose your desired output above."
|
||||
data-test-configuration-empty-state
|
||||
/>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" disabled={{true}} data-test-pki-config-save>
|
||||
Done
|
||||
</button>
|
||||
<button type="button" class="button has-left-margin-s" {{on "click" @onCancel}} data-test-pki-config-cancel>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -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<Args> {
|
||||
export default class PkiConfigureCreate extends Component<Args> {
|
||||
@service declare readonly store: Store;
|
||||
@service declare readonly router: Router;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@tracked title = 'Configure PKI';
|
||||
|
||||
get configTypes() {
|
||||
return [
|
||||
{
|
|
@ -0,0 +1,17 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-page-title>
|
||||
{{this.title}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiGenerateCsr
|
||||
@model={{@model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onSave={{fn (mut this.title) "View generated CSR"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
/>
|
|
@ -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';
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-page-title>
|
||||
{{this.title}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiGenerateRoot
|
||||
@model={{@model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onSave={{fn (mut this.title) "View generated root"}}
|
||||
@adapterOptions={{hash actionType="generate-root" useIssuer=@model.canGenerateIssuerRoot}}
|
||||
/>
|
|
@ -0,0 +1,6 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class PagePkiIssuerGenerateRootComponent extends Component {
|
||||
@tracked title = 'Generate root';
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-page-title>
|
||||
{{this.title}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiImportPemBundle
|
||||
@model={{@model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onSave={{fn (mut this.title) "View imported items"}}
|
||||
@adapterOptions={{hash actionType="import" useIssuer=true}}
|
||||
/>
|
|
@ -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';
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<div class="box is-bottomless is-fullwidth is-marginless">
|
||||
{{#unless @config.id}}
|
||||
<div class="columns">
|
||||
{{#each this.configTypes as |option|}}
|
||||
<div class="column is-flex">
|
||||
<label for={{option.key}} class="box-label is-column {{if (eq @config.actionType option.key) 'is-selected'}}">
|
||||
<div>
|
||||
<h3 class="box-label-header title is-6">
|
||||
<Icon @size="24" @name={{option.icon}} />
|
||||
{{option.label}}
|
||||
</h3>
|
||||
<p class="help has-text-grey-dark">
|
||||
{{option.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<RadioButton
|
||||
id={{option.key}}
|
||||
name="pki-config-type"
|
||||
@value={{option.key}}
|
||||
@groupValue={{@config.actionType}}
|
||||
@onChange={{fn (mut @config.actionType) option.key}}
|
||||
data-test-pki-config-option={{option.key}}
|
||||
/>
|
||||
<label for={{option.key}}></label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{#if (eq @config.actionType "import")}}
|
||||
<PkiImportPemBundle
|
||||
@model={{@config}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers"}}
|
||||
@adapterOptions={{hash actionType=@config.actionType useIssuer=@config.canImportBundle}}
|
||||
/>
|
||||
{{else if (eq @config.actionType "generate-root")}}
|
||||
<PkiGenerateRoot
|
||||
@model={{@config}}
|
||||
@urls={{@urls}}
|
||||
@onCancel={{@onCancel}}
|
||||
@adapterOptions={{hash actionType=@config.actionType useIssuer=@config.canGenerateIssuerRoot}}
|
||||
/>
|
||||
{{else if (eq @config.actionType "generate-csr")}}
|
||||
<PkiGenerateCsr
|
||||
@model={{@config}}
|
||||
@onCancel={{@onCancel}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.issuers"}}
|
||||
/>
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="Choose an option"
|
||||
@message="To see configuration options, choose your desired output above."
|
||||
data-test-configuration-empty-state
|
||||
/>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" disabled={{true}} data-test-pki-config-save>
|
||||
Done
|
||||
</button>
|
||||
<button type="button" class="button has-left-margin-s" {{on "click" @onCancel}} data-test-pki-config-cancel>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -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
|
||||
* <PkiGenerateRoot @model={{this.model}} @onCancel={{transition-to "vault.cluster"}} @onSave={{fn (mut this.title) "Successful"}} @adapterOptions={{hash actionType="import" useIssuer=false}} />
|
||||
* ```
|
||||
*
|
||||
* @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<Args> {
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
|
@ -64,12 +84,15 @@ export default class PkiGenerateCsrComponent extends Component<Args> {
|
|||
*save(event: Event): Generator<Promise<boolean | PkiActionModel>> {
|
||||
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;
|
||||
|
|
|
@ -1,69 +1,126 @@
|
|||
<form {{on "submit" (perform this.save)}} data-test-pki-config-generate-root-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-generate-root-title="Root parameters">
|
||||
Root parameters
|
||||
</h2>
|
||||
{{#each this.defaultFields as |field|}}
|
||||
{{#let (find-by "name" field @model.allFields) as |attr|}}
|
||||
<FormField @attr={{attr}} @model={{@model}} @modelValidations={{this.modelValidations}} data-test-field>
|
||||
{{#if (eq field "customTtl")}}
|
||||
{{! customTtl attr has editType yield, which will render this }}
|
||||
<PkiNotValidAfterForm @attr={{attr}} @model={{@model}} />
|
||||
{{/if}}
|
||||
</FormField>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
|
||||
<PkiGenerateToggleGroups @model={{@model}} />
|
||||
|
||||
{{#if @urls}}
|
||||
<fieldset class="box is-shadowless is-marginless is-borderless is-fullwidth" data-test-urls-section>
|
||||
<h2
|
||||
class="title is-size-5 page-header {{if @urls.canCreate 'has-border-bottom-light' 'is-borderless'}}"
|
||||
data-test-generate-root-title="Issuer URLs"
|
||||
>
|
||||
Issuer URLs
|
||||
</h2>
|
||||
{{#if @urls.canSet}}
|
||||
{{#each @urls.allFields as |attr|}}
|
||||
{{#if (not (eq attr.name "mountPath"))}}
|
||||
<FormField
|
||||
@attr={{attr}}
|
||||
@mode="create"
|
||||
@model={{@urls}}
|
||||
@showHelpText={{attr.options.showHelpText}}
|
||||
data-test-urls-field
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="You do not have permissions to set URLs."
|
||||
@message="These are not required but will need to be configured later. You can do this via the CLI or by changing your permissions and returning to this form."
|
||||
/>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary" data-test-pki-generate-root-save>
|
||||
Done
|
||||
</button>
|
||||
<button {{on "click" @onCancel}} type="button" class="button has-left-margin-s" data-test-pki-generate-root-cancel>
|
||||
Cancel
|
||||
</button>
|
||||
{{! Show results if model has an ID, which is only generated after save }}
|
||||
{{#if @model.id}}
|
||||
<Toolbar />
|
||||
{{#if @model.privateKey}}
|
||||
<div class="has-top-margin-m">
|
||||
<AlertBanner
|
||||
@title="Next steps"
|
||||
@type="warning"
|
||||
@message="The private_key is only available once. Make sure you copy and save it now."
|
||||
/>
|
||||
</div>
|
||||
{{#if this.invalidFormAlert}}
|
||||
{{/if}}
|
||||
<main class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
{{#each this.returnedFields as |field|}}
|
||||
{{#let (find-by "name" field @model.allFields) as |attr|}}
|
||||
{{#if attr.options.detailLinkTo}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
>
|
||||
<LinkTo @route={{attr.options.detailLinkTo}} @model={{get @model attr.name}}>{{get @model attr.name}}</LinkTo>
|
||||
</InfoTableRow>
|
||||
{{else if attr.options.masked}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
>
|
||||
<MaskedInput @value={{get @model attr.name}} @displayOnly={{true}} @allowCopy={{true}} />
|
||||
</InfoTableRow>
|
||||
{{else}}
|
||||
<InfoTableRow
|
||||
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
|
||||
@value={{get @model attr.name}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
<InfoTableRow @label="Private key">
|
||||
{{#if @model.privateKey}}
|
||||
<MaskedInput @value={{@model.privateKey}} @displayOnly={{true}} @allowCopy={{true}} />
|
||||
{{else}}
|
||||
<span class="tag">internal</span>
|
||||
{{/if}}
|
||||
</InfoTableRow>
|
||||
<InfoTableRow @label="Private key type" @value={{@model.privateKeyType}}>
|
||||
<span class="{{unless @model.privateKeyType 'tag'}}">{{or @model.privateKeyType "internal"}}</span>
|
||||
</InfoTableRow>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="field is-grouped is-fullwidth has-top-margin-l">
|
||||
<div class="control">
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
@paddingTop={{true}}
|
||||
@message={{this.invalidFormAlert}}
|
||||
@mimicRefresh={{true}}
|
||||
data-test-pki-generate-root-validation-error
|
||||
/>
|
||||
<button type="button" class="button is-primary" {{on "click" @onComplete}} data-test-done>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{else}}
|
||||
<form {{on "submit" (perform this.save)}} data-test-pki-config-generate-root-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-generate-root-title="Root parameters">
|
||||
Root parameters
|
||||
</h2>
|
||||
{{#each this.defaultFields as |field|}}
|
||||
{{#let (find-by "name" field @model.allFields) as |attr|}}
|
||||
<FormField @attr={{attr}} @model={{@model}} @modelValidations={{this.modelValidations}} data-test-field>
|
||||
{{#if (eq field "customTtl")}}
|
||||
{{! customTtl attr has editType yield, which will render this }}
|
||||
<PkiNotValidAfterForm @attr={{attr}} @model={{@model}} />
|
||||
{{/if}}
|
||||
</FormField>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
|
||||
<PkiGenerateToggleGroups @model={{@model}} />
|
||||
|
||||
{{#if @urls}}
|
||||
<fieldset class="box is-shadowless is-marginless is-borderless is-fullwidth" data-test-urls-section>
|
||||
<h2
|
||||
class="title is-size-5 page-header {{if @urls.canCreate 'has-border-bottom-light' 'is-borderless'}}"
|
||||
data-test-generate-root-title="Issuer URLs"
|
||||
>
|
||||
Issuer URLs
|
||||
</h2>
|
||||
{{#if @urls.canSet}}
|
||||
{{#each @urls.allFields as |attr|}}
|
||||
{{#if (not (eq attr.name "mountPath"))}}
|
||||
<FormField
|
||||
@attr={{attr}}
|
||||
@mode="create"
|
||||
@model={{@urls}}
|
||||
@showHelpText={{attr.options.showHelpText}}
|
||||
data-test-urls-field
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="You do not have permissions to set URLs."
|
||||
@message="These are not required but will need to be configured later. You can do this via the CLI or by changing your permissions and returning to this form."
|
||||
/>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary" data-test-pki-generate-root-save>
|
||||
Done
|
||||
</button>
|
||||
<button {{on "click" @onCancel}} type="button" class="button has-left-margin-s" data-test-pki-generate-root-cancel>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<div class="control">
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
@paddingTop={{true}}
|
||||
@message={{this.invalidFormAlert}}
|
||||
@mimicRefresh={{true}}
|
||||
data-test-pki-generate-root-validation-error
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
{{/if}}
|
|
@ -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
|
||||
* <PkiGenerateRoot @model={{this.model}} @onCancel={{transition-to "vault.cluster"}} @onSave={{transition-to "vault.cluster.secrets"}} @adapterOptions={{hash actionType="import" useIssuer=false}} />
|
||||
* <PkiGenerateRoot @model={{this.model}} @onCancel={{transition-to "vault.cluster"}} @onSave={{fn (mut this.title) "Successful"}} @adapterOptions={{hash actionType="import" useIssuer=false}} />
|
||||
* ```
|
||||
*
|
||||
* @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<Args> {
|
||||
@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.';
|
|
@ -1,36 +1,87 @@
|
|||
<div class="field">
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5">
|
||||
Certificate parameters
|
||||
</label>
|
||||
<form {{on "submit" (perform this.submitForm)}} data-test-pki-import-pem-bundle-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<div class="box is-sideless is-fullwidth is-marginless has-top-padding-l">
|
||||
<TextFile @onChange={{this.onFileUploaded}} @label="PEM Bundle" />
|
||||
<p class="has-top-margin-m has-bottom-margin-l">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div class="has-top-padding-s">
|
||||
<button
|
||||
type="submit"
|
||||
class="button is-primary {{if this.submitForm.isRunning 'is-loading'}}"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
data-test-pki-import-pem-bundle
|
||||
>
|
||||
Import issuer
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
data-test-pki-ca-cert-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{#if this.importedResponse}}
|
||||
<Toolbar />
|
||||
<div class="is-flex-start has-top-margin-xs">
|
||||
<div class="is-flex-1 basis-0 has-text-grey has-bottom-margin-xs">
|
||||
<h2>
|
||||
Imported Issuer
|
||||
</h2>
|
||||
</div>
|
||||
<div class="is-flex-1 basis-0 has-text-grey has-bottom-margin-xs">
|
||||
<h2>
|
||||
Imported Key
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box is-fullwidth is-sideless is-marginless is-paddingless" data-test-imported-bundle-mapping>
|
||||
{{#each-in this.importedResponse as |issuer key|}}
|
||||
<div class="box is-marginless no-top-shadow has-slim-padding">
|
||||
<div class="is-flex-start">
|
||||
<div class="is-flex-1 basis-0 has-bottom-margin-xs" data-test-imported-issuer>
|
||||
{{#if issuer}}
|
||||
<LinkTo @route="issuers.issuer.details" @model={{issuer}}>
|
||||
{{issuer}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
None
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="is-flex-1 basis-0 has-bottom-margin-xs" data-test-imported-key>
|
||||
{{#if key}}
|
||||
<LinkTo @route="keys.key.details" @model={{key}}>
|
||||
{{key}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
None
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
<footer>
|
||||
<div class="field is-grouped is-fullwidth has-top-margin-l">
|
||||
<div class="control">
|
||||
<button type="button" class="button is-primary" {{on "click" @onComplete}} data-test-done>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{else}}
|
||||
<div class="field">
|
||||
<div class="form-section">
|
||||
<label class="title has-padding-top is-5" data-test-import-section-label>
|
||||
Certificate parameters
|
||||
</label>
|
||||
<form {{on "submit" (perform this.submitForm)}} data-test-pki-import-pem-bundle-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<div class="box is-sideless is-fullwidth is-marginless has-top-padding-l">
|
||||
<TextFile @onChange={{this.onFileUploaded}} @label="PEM Bundle" />
|
||||
<p class="has-top-margin-m has-bottom-margin-l">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div class="has-top-padding-s">
|
||||
<button
|
||||
type="submit"
|
||||
class="button is-primary {{if this.submitForm.isRunning 'is-loading'}}"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
data-test-pki-import-pem-bundle
|
||||
>
|
||||
Import issuer
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
data-test-pki-ca-cert-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -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<Args> {
|
|||
|
||||
@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);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-configuration-page-title>
|
||||
Configure PKI
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiConfigureForm
|
||||
<Page::PkiConfigureCreate
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
@config={{this.model.config}}
|
||||
@urls={{this.model.urls}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.overview"}}
|
||||
|
|
|
@ -1,16 +1 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
Generate intermediate CSR
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiGenerateCsr
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onComplete={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
/>
|
||||
<Page::PkiIssuerGenerateIntermediate @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
|
@ -1,16 +1 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-issuers-page-title>
|
||||
Generate root
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiGenerateRoot
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@adapterOptions={{hash actionType="generate-root" useIssuer=this.model.canGenerateIssuerRoot}}
|
||||
/>
|
||||
<Page::PkiIssuerGenerateRoot @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
|
@ -1,16 +1 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-issuer-page-title>
|
||||
Import a CA
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
<PkiImportPemBundle
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.issuers.index"}}
|
||||
@adapterOptions={{hash actionType="import" useIssuer=true}}
|
||||
/>
|
||||
<Page::PkiIssuerImport @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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');
|
|
@ -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
|
||||
|
|
|
@ -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]',
|
||||
};
|
|
@ -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"]',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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]',
|
||||
|
|
|
@ -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`<PkiConfigureForm @onCancel={{this.cancelSpy}} @config={{this.config}} />`, {
|
||||
owner: this.engine,
|
||||
});
|
||||
|
||||
await render(
|
||||
hbs`
|
||||
<Page::PkiConfigureCreate
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
@config={{this.config}}
|
||||
@urls={{this.urls}}
|
||||
@onCancel={{this.cancelSpy}}
|
||||
/>
|
||||
`,
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -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`<Page::PkiIssuerGenerateIntermediate @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{
|
||||
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`<Page::PkiIssuerGenerateIntermediate @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -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`<Page::PkiIssuerGenerateRoot @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{
|
||||
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`<Page::PkiIssuerGenerateRoot @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -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`<Page::PkiIssuerImport @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`, {
|
||||
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`<Page::PkiIssuerImport @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />`, {
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -28,3 +28,9 @@ export interface ModelValidation {
|
|||
};
|
||||
invalidFormMessage: string;
|
||||
}
|
||||
|
||||
export interface Breadcrumb {
|
||||
label: string;
|
||||
route?: string;
|
||||
linkExternal?: boolean;
|
||||
}
|
||||
|
|
|
@ -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<CapabilitiesModel>;
|
||||
|
|
Loading…
Reference in New Issue