mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
UI: VAULT-9408 Delete all issuers toolbar action + modal (#19756)
This commit is contained in:
@@ -47,4 +47,10 @@ export default class PkiIssuerAdapter extends ApplicationAdapter {
|
||||
const { backend, id } = query;
|
||||
return this.ajax(this.urlForQuery(backend, id), 'GET', this.optionsForQuery(id));
|
||||
}
|
||||
|
||||
deleteAllIssuers(backend) {
|
||||
const deleteAllIssuersAndKeysUrl = `${this.buildURL()}/${encodePath(backend)}/root`;
|
||||
|
||||
return this.ajax(deleteAllIssuersAndKeysUrl, 'DELETE');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ export default class PkiIssuerModel extends Model {
|
||||
@lazyCapabilities(apiPath`${'backend'}/root/rotate/exported`) rotateExported;
|
||||
@lazyCapabilities(apiPath`${'backend'}/root/rotate/internal`) rotateInternal;
|
||||
@lazyCapabilities(apiPath`${'backend'}/root/rotate/existing`) rotateExisting;
|
||||
@lazyCapabilities(apiPath`${'backend'}/root`, 'backend') deletePath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/intermediate/cross-sign`) crossSignPath;
|
||||
@lazyCapabilities(apiPath`${'backend'}/issuer/${'issuerId'}/sign-intermediate`) signIntermediate;
|
||||
get canRotateIssuer() {
|
||||
@@ -168,4 +169,7 @@ export default class PkiIssuerModel extends Model {
|
||||
get canConfigure() {
|
||||
return this.issuerPath.get('canUpdate') !== false;
|
||||
}
|
||||
get canDeleteAllIssuers() {
|
||||
return this.deletePath.get('isLoading') || this.deletePath.get('canDelete') !== false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,9 @@
|
||||
.has-background-gray-200 {
|
||||
background-color: $ui-gray-200;
|
||||
}
|
||||
.has-background-gray-100 {
|
||||
background-color: $ui-gray-100;
|
||||
}
|
||||
@each $name, $pair in $colors {
|
||||
$color: nth($pair, 1);
|
||||
.has-background-#{$name} {
|
||||
|
||||
@@ -1,25 +1,56 @@
|
||||
<h2 class="title has-bottom-margin-xs has-top-margin-m is-4 has-border-bottom-light has-bottom-padding-s">
|
||||
Global URLs
|
||||
</h2>
|
||||
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />
|
||||
<InfoTableRow
|
||||
@label="CRL distribution points"
|
||||
@value={{if @urls.crlDistributionPoints @urls.crlDistributionPoints "None"}}
|
||||
/>
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
{{#if @canDeleteAllIssuers}}
|
||||
<button
|
||||
type="button"
|
||||
class="toolbar-link"
|
||||
{{on "click" (fn (mut this.showDeleteAllIssuers) true)}}
|
||||
data-test-delete-all-issuers-link
|
||||
>
|
||||
Delete all issuers
|
||||
<Icon @name="chevron-right" />
|
||||
|
||||
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
|
||||
Certificate Revocation List (CRL)
|
||||
</h2>
|
||||
<InfoTableRow @label="Expiry" @value={{@crl.expiry}} />
|
||||
<InfoTableRow @label="Auto-rebuild" @value={{if @crl.autoRebuild "On" "Off"}} />
|
||||
</button>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{/if}}
|
||||
<ToolbarLink @route="configuration.tidy">
|
||||
Tidy
|
||||
</ToolbarLink>
|
||||
<ToolbarLink @route="configuration.edit">
|
||||
Edit configuration
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
|
||||
Online Certificate Status Protocol (OCSP)
|
||||
</h2>
|
||||
<InfoTableRow @label="Responder APIs" @value={{if @crl.ocspDisable "Disabled" "Enabled"}} />
|
||||
<InfoTableRow @label="Interval" @value={{@crl.ocspExpiry}} />
|
||||
{{#if (not (eq @urls 403))}}
|
||||
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-m has-border-bottom-light has-bottom-padding-s">
|
||||
Global URLs
|
||||
</h2>
|
||||
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />
|
||||
<InfoTableRow
|
||||
@label="CRL distribution points"
|
||||
@value={{if @urls.crlDistributionPoints @urls.crlDistributionPoints "None"}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<h2 class="title has-bottom-margin-xs has-top-margin-xl is-4 has-border-bottom-light has-bottom-padding-s">
|
||||
{{#if (not (eq @crl 403))}}
|
||||
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
|
||||
Certificate Revocation List (CRL)
|
||||
</h2>
|
||||
<InfoTableRow @label="Expiry" @value={{@crl.expiry}} />
|
||||
<InfoTableRow @label="Auto-rebuild" @value={{if @crl.autoRebuild "On" "Off"}} />
|
||||
|
||||
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
|
||||
Online Certificate Status Protocol (OCSP)
|
||||
</h2>
|
||||
<InfoTableRow @label="Responder APIs" @value={{if @crl.ocspDisable "Disabled" "Enabled"}} />
|
||||
<InfoTableRow @label="Interval" @value={{@crl.ocspExpiry}} />
|
||||
{{/if}}
|
||||
|
||||
<h2
|
||||
class="title is-4 has-bottom-margin-xs has-border-bottom-light has-bottom-padding-s
|
||||
{{if (and (eq @crl 403) (eq @urls 403)) 'has-top-margin-m' 'has-top-margin-xl'}}"
|
||||
>
|
||||
Mount Configuration
|
||||
</h2>
|
||||
<InfoTableRow @label="Secret engine type" @value={{@mountConfig.engineType}} />
|
||||
@@ -30,4 +61,23 @@
|
||||
<InfoTableRow @label="Default lease TTL" @value={{@mountConfig.config.defaultLeaseTtl}} />
|
||||
<InfoTableRow @label="Max lease TTL" @value={{@mountConfig.config.maxLeaseTtl}} />
|
||||
<InfoTableRow @label="Allowed managed keys" @value={{or @mountConfig.config.allowedManagedKeys "None"}} />
|
||||
<div class="has-top-margin-l"></div>
|
||||
<div class="has-top-margin-l"></div>
|
||||
|
||||
{{#if this.showDeleteAllIssuers}}
|
||||
<ConfirmationModal
|
||||
@title="Delete All Issuers?"
|
||||
@toConfirmMsg="deleting all issuers and keys."
|
||||
@buttonText="Confirm"
|
||||
@confirmText="delete-all"
|
||||
@isActive={{this.showDeleteAllIssuers}}
|
||||
@onClose={{action (mut this.showDeleteAllIssuers) false}}
|
||||
@onConfirm={{this.deleteAllIssuers}}
|
||||
>
|
||||
<section class="modal-card-custom">
|
||||
This endpoint deletes
|
||||
<strong>all</strong>
|
||||
issuers and keys within the mount. It is highly recommended to use the individual delete operations instead. This mount
|
||||
will be unusable until new issuers and keys are provisioned.
|
||||
</section>
|
||||
</ConfirmationModal>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import errorMessage from 'vault/utils/error-message';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
//TYPES
|
||||
import RouterService from '@ember/routing/router-service';
|
||||
import FlashMessageService from 'vault/services/flash-messages';
|
||||
import Store from '@ember-data/store';
|
||||
|
||||
interface Args {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
export default class PkiConfigurationDetails extends Component<Args> {
|
||||
@service declare readonly store: Store;
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@tracked showDeleteAllIssuers = false;
|
||||
|
||||
@action
|
||||
async deleteAllIssuers() {
|
||||
try {
|
||||
const issuerAdapter = this.store.adapterFor('pki/issuer');
|
||||
await issuerAdapter.deleteAllIssuers(this.args.currentPath);
|
||||
this.flashMessages.success('Successfully deleted all issuers and keys');
|
||||
this.showDeleteAllIssuers = false;
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.pki.configuration.index');
|
||||
} catch (error) {
|
||||
this.showDeleteAllIssuers = false;
|
||||
this.flashMessages.danger(errorMessage(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-config';
|
||||
import { hash } from 'rsvp';
|
||||
import { getCliMessage } from 'pki/routes/overview';
|
||||
|
||||
@withConfig()
|
||||
export default class PkiCertificatesIndexRoute extends Route {
|
||||
@@ -32,4 +33,12 @@ export default class PkiCertificatesIndexRoute extends Route {
|
||||
parentModel: this.modelFor('certificates'),
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const certificates = resolvedModel.certificates;
|
||||
|
||||
if (certificates?.length) controller.message = getCliMessage('certificates');
|
||||
else controller.message = getCliMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,27 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-config';
|
||||
import { hash } from 'rsvp';
|
||||
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
|
||||
|
||||
@withConfig()
|
||||
export default class ConfigurationIndexRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
model() {
|
||||
const backend = this.secretMountPath.currentPath;
|
||||
return hash({
|
||||
hasConfig: this.shouldPromptConfig,
|
||||
engine: this.modelFor('application'),
|
||||
urls: this.store.findRecord('pki/urls', backend),
|
||||
crl: this.store.findRecord('pki/crl', backend),
|
||||
mountConfig: this.fetchMountConfig(backend),
|
||||
});
|
||||
async fetchUrls(backend) {
|
||||
try {
|
||||
return await this.store.findRecord('pki/urls', backend);
|
||||
} catch (e) {
|
||||
return e.httpStatus;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchCrl(backend) {
|
||||
try {
|
||||
return await this.store.findRecord('pki/crl', backend);
|
||||
} catch (e) {
|
||||
return e.httpStatus;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMountConfig(path) {
|
||||
@@ -31,4 +37,23 @@ export default class ConfigurationIndexRoute extends Route {
|
||||
return mountConfig.get('firstObject');
|
||||
}
|
||||
}
|
||||
|
||||
async model() {
|
||||
const backend = this.secretMountPath.currentPath;
|
||||
|
||||
return hash({
|
||||
hasConfig: this.shouldPromptConfig,
|
||||
engine: this.modelFor('application'),
|
||||
urls: this.fetchUrls(backend),
|
||||
crl: this.fetchCrl(backend),
|
||||
mountConfig: this.fetchMountConfig(backend),
|
||||
issuerModel: this.store.createRecord('pki/issuer'),
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
|
||||
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
|
||||
|
||||
export default class PkiIssuersListRoute extends Route {
|
||||
@service store;
|
||||
@@ -38,5 +39,6 @@ export default class PkiIssuersListRoute extends Route {
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview' },
|
||||
{ label: 'issuers', route: 'issuers.index' },
|
||||
];
|
||||
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-config';
|
||||
import { hash } from 'rsvp';
|
||||
import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
|
||||
|
||||
@withConfig()
|
||||
export default class PkiKeysIndexRoute extends Route {
|
||||
@@ -40,5 +41,6 @@ export default class PkiKeysIndexRoute extends Route {
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview' },
|
||||
{ label: 'keys', route: 'keys.index' },
|
||||
];
|
||||
controller.message = PKI_DEFAULT_EMPTY_STATE_MSG;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,29 @@ import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-config';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export const PKI_DEFAULT_EMPTY_STATE_MSG =
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer.";
|
||||
|
||||
export const getCliMessage = (msg) => {
|
||||
if (!msg) return PKI_DEFAULT_EMPTY_STATE_MSG;
|
||||
|
||||
return `${PKI_DEFAULT_EMPTY_STATE_MSG} There are existing ${msg}. Use the CLI to perform any operations with them until an issuer is configured.`;
|
||||
};
|
||||
|
||||
@withConfig()
|
||||
export default class PkiOverviewRoute extends Route {
|
||||
@service secretMountPath;
|
||||
@service auth;
|
||||
@service store;
|
||||
|
||||
async fetchAllCertificates() {
|
||||
try {
|
||||
return await this.store.query('pki/certificate/base', { backend: this.secretMountPath.currentPath });
|
||||
} catch (e) {
|
||||
return e.httpStatus;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAllRoles() {
|
||||
try {
|
||||
return await this.store.query('pki/role', { backend: this.secretMountPath.currentPath });
|
||||
@@ -36,10 +53,19 @@ export default class PkiOverviewRoute extends Route {
|
||||
engine: this.modelFor('application'),
|
||||
roles: this.fetchAllRoles(),
|
||||
issuers: this.fetchAllIssuers(),
|
||||
certificates: this.fetchAllCertificates(),
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const roles = resolvedModel.roles;
|
||||
const certificates = resolvedModel.certificates;
|
||||
|
||||
controller.message = getCliMessage();
|
||||
|
||||
if (roles?.length) controller.message = getCliMessage('roles');
|
||||
if (certificates?.length) controller.message = getCliMessage('certificates');
|
||||
if (roles?.length && certificates?.length) controller.message = getCliMessage('roles and certificates');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'pki/decorators/check-config';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import { getCliMessage } from 'pki/routes/overview';
|
||||
@withConfig()
|
||||
export default class PkiRolesIndexRoute extends Route {
|
||||
@service store;
|
||||
@@ -32,4 +32,12 @@ export default class PkiRolesIndexRoute extends Route {
|
||||
parentModel: this.modelFor('roles'),
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, resolvedModel) {
|
||||
super.setupController(controller, resolvedModel);
|
||||
const roles = resolvedModel.roles;
|
||||
|
||||
if (roles?.length) controller.message = getCliMessage('roles');
|
||||
else controller.message = getCliMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
|
||||
@@ -10,22 +10,16 @@
|
||||
/>
|
||||
|
||||
{{#if this.model.hasConfig}}
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
<ToolbarLink @route="configuration.tidy">
|
||||
Tidy
|
||||
</ToolbarLink>
|
||||
<ToolbarLink @route="configuration.edit">
|
||||
Edit configuration
|
||||
</ToolbarLink>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<Page::PkiConfigurationDetails @urls={{this.model.urls}} @crl={{this.model.crl}} @mountConfig={{this.model.mountConfig}} />
|
||||
<Page::PkiConfigurationDetails
|
||||
@urls={{this.model.urls}}
|
||||
@crl={{this.model.crl}}
|
||||
@mountConfig={{this.model.mountConfig}}
|
||||
@currentPath={{this.model.engine.id}}
|
||||
@canDeleteAllIssuers={{this.model.issuerModel.canDeleteAllIssuers}}
|
||||
/>
|
||||
{{else}}
|
||||
<Toolbar />
|
||||
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</LinkedBlock>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
{{else}}
|
||||
<Toolbar />
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
{{#if this.model.hasConfig}}
|
||||
<Page::PkiOverview @issuers={{this.model.issuers}} @roles={{this.model.roles}} @engine={{this.model.engine}} />
|
||||
{{else}}
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<LinkTo @route="configuration.create" @model={{this.model.engine}}>
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
</EmptyState>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<EmptyState @title="PKI not configured" @message="This PKI mount hasn't yet been configured with a certificate issuer.">
|
||||
<EmptyState @title="PKI not configured" @message={{this.message}}>
|
||||
<LinkTo @route="configuration.create">
|
||||
Configure PKI
|
||||
</LinkTo>
|
||||
|
||||
197
ui/tests/acceptance/pki/configuration-test.js
Normal file
197
ui/tests/acceptance/pki/configuration-test.js
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { click, currentURL, fillIn, visit, isSettled, waitUntil } from '@ember/test-helpers';
|
||||
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 } from 'vault/tests/helpers/pki/workflow';
|
||||
import { issuerPemBundle } from 'vault/tests/helpers/pki/values';
|
||||
|
||||
module('Acceptance | pki configuration', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
this.pemBundle = issuerPemBundle;
|
||||
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('delete all issuers modal and empty states', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
|
||||
test('it shows the delete all issuers modal', async function (assert) {
|
||||
await authPage.login(this.pkiAdminToken);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.emptyStateLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
|
||||
await isSettled();
|
||||
await click(SELECTORS.configuration.generateRootOption);
|
||||
await fillIn(SELECTORS.configuration.typeField, 'exported');
|
||||
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
|
||||
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
|
||||
await click(SELECTORS.configuration.generateRootSave);
|
||||
await click(SELECTORS.configuration.doneButton);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await isSettled();
|
||||
await click(SELECTORS.configTab);
|
||||
await isSettled();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.configuration.issuerLink);
|
||||
await isSettled();
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
|
||||
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
|
||||
await click(SELECTORS.configuration.deleteAllIssuerButton);
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
});
|
||||
|
||||
test('it shows the correct empty state message if certificates exists after delete all issuers', async function (assert) {
|
||||
await authPage.login(this.pkiAdminToken);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.emptyStateLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
|
||||
await click(SELECTORS.configuration.generateRootOption);
|
||||
await fillIn(SELECTORS.configuration.typeField, 'exported');
|
||||
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
|
||||
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
|
||||
await click(SELECTORS.configuration.generateRootSave);
|
||||
await click(SELECTORS.configuration.doneButton);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await click(SELECTORS.configTab);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.configuration.issuerLink);
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
|
||||
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
|
||||
await click(SELECTORS.configuration.deleteAllIssuerButton);
|
||||
await isSettled();
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await isSettled();
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await isSettled();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText(
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
|
||||
);
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/issuers`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/certificates`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText(
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
|
||||
);
|
||||
});
|
||||
|
||||
test('it shows the correct empty state message if roles and certificates exists after delete all issuers', async function (assert) {
|
||||
await authPage.login(this.pkiAdminToken);
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.emptyStateLink);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration/create`);
|
||||
await click(SELECTORS.configuration.generateRootOption);
|
||||
await fillIn(SELECTORS.configuration.typeField, 'exported');
|
||||
await fillIn(SELECTORS.configuration.generateRootCommonNameField, 'issuer-common-0');
|
||||
await fillIn(SELECTORS.configuration.generateRootIssuerNameField, 'issuer-0');
|
||||
await click(SELECTORS.configuration.generateRootSave);
|
||||
await click(SELECTORS.configuration.doneButton);
|
||||
await runCommands([
|
||||
`write ${this.mountPath}/roles/some-role \
|
||||
issuer_ref="default" \
|
||||
allowed_domains="example.com" \
|
||||
allow_subdomains=true \
|
||||
max_ttl="720h"`,
|
||||
]);
|
||||
await runCommands([`write ${this.mountPath}/root/generate/internal common_name="Hashicorp Test"`]);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await click(SELECTORS.configTab);
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await click(SELECTORS.configuration.issuerLink);
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).exists();
|
||||
await fillIn(SELECTORS.configuration.deleteAllIssuerInput, 'delete-all');
|
||||
await click(SELECTORS.configuration.deleteAllIssuerButton);
|
||||
await isSettled();
|
||||
assert.dom(SELECTORS.configuration.deleteAllIssuerModal).doesNotExist();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/configuration`);
|
||||
await isSettled();
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await waitUntil(() => currentURL() === `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
await isSettled();
|
||||
assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/pki/overview`);
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText(
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing roles and certificates. Use the CLI to perform any operations with them until an issuer is configured."
|
||||
);
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/roles`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText(
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing roles. Use the CLI to perform any operations with them until an issuer is configured."
|
||||
);
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/issuers`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/keys`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText("This PKI mount hasn't yet been configured with a certificate issuer.");
|
||||
|
||||
await visit(`/vault/secrets/${this.mountPath}/pki/certificates`);
|
||||
await isSettled();
|
||||
assert
|
||||
.dom(SELECTORS.emptyStateMessage)
|
||||
.hasText(
|
||||
"This PKI mount hasn't yet been configured with a certificate issuer. There are existing certificates. Use the CLI to perform any operations with them until an issuer is configured."
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,8 +13,10 @@ export const SELECTORS = {
|
||||
optionByKey: (key) => `[data-test-pki-config-option="${key}"]`,
|
||||
cancelButton: '[data-test-pki-config-cancel]',
|
||||
saveButton: '[data-test-pki-config-save]',
|
||||
doneButton: '[data-test-done]',
|
||||
// pki-generate-root
|
||||
...GENERATE_ROOT,
|
||||
generateRootOption: '[data-test-pki-config-option="generate-root"]',
|
||||
// pki-ca-cert-import
|
||||
importForm: '[data-test-pki-import-pem-bundle-form]',
|
||||
importSubmit: '[data-test-pki-import-pem-bundle]',
|
||||
|
||||
11
ui/tests/helpers/pki/pki-delete-all-issuers.js
Normal file
11
ui/tests/helpers/pki/pki-delete-all-issuers.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
export const SELECTORS = {
|
||||
issuerLink: '[data-test-delete-all-issuers-link]',
|
||||
deleteAllIssuerModal: '[data-test-modal-background="Delete All Issuers?"]',
|
||||
deleteAllIssuerInput: '[data-test-confirmation-modal-input="Delete All Issuers?"]',
|
||||
deleteAllIssuerButton: '[data-test-confirm-button="Delete All Issuers?"]',
|
||||
};
|
||||
@@ -16,6 +16,8 @@ export const SELECTORS = {
|
||||
fieldByName: (name) => `[data-test-field="${name}"]`,
|
||||
generateRootSave: '[data-test-pki-generate-root-save]',
|
||||
generateRootCancel: '[data-test-pki-generate-root-cancel]',
|
||||
generateRootCommonNameField: '[data-test-input="commonName"]',
|
||||
generateRootIssuerNameField: '[data-test-input="issuerName"]',
|
||||
formInvalidError: '[data-test-pki-generate-root-validation-error]',
|
||||
urlsSection: '[data-test-urls-section]',
|
||||
urlField: '[data-test-urls-section] [data-test-input]',
|
||||
|
||||
@@ -9,6 +9,7 @@ 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-create';
|
||||
import { SELECTORS as DELETE } from './pki-delete-all-issuers';
|
||||
|
||||
export const SELECTORS = {
|
||||
breadcrumbContainer: '[data-test-breadcrumbs]',
|
||||
@@ -16,6 +17,7 @@ export const SELECTORS = {
|
||||
overviewBreadcrumb: '[data-test-breadcrumbs] li:nth-of-type(2) > a',
|
||||
pageTitle: '[data-test-pki-role-page-title]',
|
||||
alertBanner: '[data-test-alert-banner="alert"]',
|
||||
emptyState: '[data-test-component="empty-state"]',
|
||||
emptyStateTitle: '[data-test-empty-state-title]',
|
||||
emptyStateLink: '.empty-state-actions a',
|
||||
emptyStateMessage: '[data-test-empty-state-message]',
|
||||
@@ -63,5 +65,6 @@ export const SELECTORS = {
|
||||
pkiBetaBanner: '[data-test-pki-configuration-banner]',
|
||||
pkiBetaBannerLink: '[data-test-pki-configuration-banner] a',
|
||||
...CONFIGURATION,
|
||||
...DELETE,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
import Application from 'vault/adapters/application';
|
||||
import Adapter from 'ember-data/adapter';
|
||||
import ModelRegistry from 'ember-data/types/registries/model';
|
||||
import PkiIssuerAdapter from 'vault/adapters/pki/issuer';
|
||||
|
||||
/**
|
||||
* Catch-all for ember-data.
|
||||
*/
|
||||
export default interface AdapterRegistry {
|
||||
'pki/issuer': PkiIssuerAdapter;
|
||||
application: Application;
|
||||
[key: keyof ModelRegistry]: Adapter;
|
||||
}
|
||||
|
||||
12
ui/types/vault/adapters/pki/issuer.d.ts
vendored
Normal file
12
ui/types/vault/adapters/pki/issuer.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Store from '@ember-data/store';
|
||||
import { AdapterRegistry } from 'ember-data/adapter';
|
||||
|
||||
export default interface PkiIssuerAdapter extends AdapterRegistry {
|
||||
namespace: string;
|
||||
deleteAllIssuers(backend: string);
|
||||
}
|
||||
Reference in New Issue
Block a user