UI: add pki cluster config parameters (#20724)

* add config directory, rename crl and urls models

* fix imports

* add cluster config fields to edit form

* reorder url save

* update tests

* add to details page

* add details test;

* fix adapter name

* fix cluster adapter test name

* combine adapter tests

* update imports

* fix git diff

* move crl and urls adapters to config folder

* add config file

* woops add config adapter

* final renaming!!

* fix imports after naming to base

* add cluster to beforeModel hook

* hide help text

* maybe you should write tests that actually pass, claire

* seriously claire its embarrassing
This commit is contained in:
claire bontempo 2023-05-23 15:24:53 -07:00 committed by GitHub
parent 83d32240c7
commit 527f4fe2ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 278 additions and 161 deletions

View File

@ -3,16 +3,11 @@
* SPDX-License-Identifier: MPL-2.0 * SPDX-License-Identifier: MPL-2.0
*/ */
import { encodePath } from 'vault/utils/path-encoding-helpers'; import ApplicationAdapter from '../../application';
import ApplicationAdapter from '../application';
export default class PkiCrlAdapter extends ApplicationAdapter { export default class PkiConfigBaseAdapter extends ApplicationAdapter {
namespace = 'v1'; namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/crl`;
}
findRecord(store, type, backend) { findRecord(store, type, backend) {
return this.ajax(this._url(backend), 'GET').then((resp) => { return this.ajax(this._url(backend), 'GET').then((resp) => {
return resp.data; return resp.data;

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { encodePath } from 'vault/utils/path-encoding-helpers';
import PkiConfigBaseAdapter from './base';
export default class PkiConfigClusterAdapter extends PkiConfigBaseAdapter {
namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/cluster`;
}
}

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { encodePath } from 'vault/utils/path-encoding-helpers';
import PkiConfigBaseAdapter from './base';
export default class PkiConfigCrlAdapter extends PkiConfigBaseAdapter {
namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/crl`;
}
}

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { encodePath } from 'vault/utils/path-encoding-helpers';
import PkiConfigBaseAdapter from './base';
export default class PkiConfigUrlsAdapter extends PkiConfigBaseAdapter {
namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/urls`;
}
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { encodePath } from 'vault/utils/path-encoding-helpers';
import ApplicationAdapter from '../application';
export default class PkiUrlsAdapter extends ApplicationAdapter {
namespace = 'v1';
_url(backend) {
return `${this.buildURL()}/${encodePath(backend)}/config/urls`;
}
updateRecord(store, type, snapshot) {
const data = snapshot.serialize();
return this.ajax(this._url(snapshot.record.id), 'POST', { data });
}
urlForFindRecord(id) {
return this._url(id);
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model, { attr } from '@ember-data/model';
import { withFormFields } from 'vault/decorators/model-form-fields';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@withFormFields()
export default class PkiConfigClusterModel extends Model {
// This model uses the backend value as the model ID
get useOpenAPI() {
return true;
}
getHelpUrl(backendPath) {
return `/v1/${backendPath}/config/cluster?help=1`;
}
@attr('string', {
label: "Mount's API path",
subText:
"Specifies the path to this performance replication cluster's API mount path, including any namespaces as path components. This address is used for the ACME directories, which must be served over a TLS-enabled listener.",
})
path;
@attr('string', {
label: 'AIA path',
subText:
"Specifies the path to this performance replication cluster's AIA distribution point; may refer to an external, non-Vault responder.",
})
aiaPath;
// this is for pki-only cluster config, not the universal vault cluster
@lazyCapabilities(apiPath`${'id'}/config/cluster`, 'id') clusterPath;
get canSet() {
return this.clusterPath.get('canCreate') !== false;
}
}

View File

@ -17,7 +17,7 @@ const formFieldGroups = [
{ 'Unified Revocation': ['crossClusterRevocation', 'unifiedCrl', 'unifiedCrlOnExistingPaths'] }, { 'Unified Revocation': ['crossClusterRevocation', 'unifiedCrl', 'unifiedCrlOnExistingPaths'] },
]; ];
@withFormFields(null, formFieldGroups) @withFormFields(null, formFieldGroups)
export default class PkiCrlModel extends Model { export default class PkiConfigCrlModel extends Model {
// This model uses the backend value as the model ID // This model uses the backend value as the model ID
@attr('boolean') autoRebuild; @attr('boolean') autoRebuild;

View File

@ -8,7 +8,7 @@ import { withFormFields } from 'vault/decorators/model-form-fields';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
@withFormFields() @withFormFields()
export default class PkiUrlsModel extends Model { export default class PkiConfigUrlsModel extends Model {
// This model uses the backend value as the model ID // This model uses the backend value as the model ID
get useOpenAPI() { get useOpenAPI() {
return true; return true;

View File

@ -23,8 +23,20 @@
</ToolbarActions> </ToolbarActions>
</Toolbar> </Toolbar>
{{#if (not (eq @cluster 403))}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Cluster Config
</h2>
{{#each @cluster.allFields as |attr|}}
<InfoTableRow
@label={{or attr.options.label (humanize (dasherize attr.name))}}
@value={{or (get @cluster attr.name) "None"}}
/>
{{/each}}
{{/if}}
{{#if (not (eq @urls 403))}} {{#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"> <h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Global URLs Global URLs
</h2> </h2>
<InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} /> <InfoTableRow @label="Issuing certificates" @value={{or @urls.issuingCertificates "None"}} />

View File

@ -14,7 +14,7 @@ import type Store from '@ember-data/store';
import type VersionService from 'vault/services/version'; import type VersionService from 'vault/services/version';
interface Args { interface Args {
currentPath: string; backend: string;
} }
export default class PkiConfigurationDetails extends Component<Args> { export default class PkiConfigurationDetails extends Component<Args> {
@ -32,7 +32,7 @@ export default class PkiConfigurationDetails extends Component<Args> {
async deleteAllIssuers() { async deleteAllIssuers() {
try { try {
const issuerAdapter = this.store.adapterFor('pki/issuer'); const issuerAdapter = this.store.adapterFor('pki/issuer');
await issuerAdapter.deleteAllIssuers(this.args.currentPath); await issuerAdapter.deleteAllIssuers(this.args.backend);
this.flashMessages.success('Successfully deleted all issuers and keys'); this.flashMessages.success('Successfully deleted all issuers and keys');
this.showDeleteAllIssuers = false; this.showDeleteAllIssuers = false;
this.router.transitionTo('vault.cluster.secrets.backend.pki.configuration.index'); this.router.transitionTo('vault.cluster.secrets.backend.pki.configuration.index');

View File

@ -3,6 +3,25 @@
<AlertBanner @type="danger" @message={{this.errorBanner}} data-test-error-banner /> <AlertBanner @type="danger" @message={{this.errorBanner}} data-test-error-banner />
{{/if}} {{/if}}
<form {{on "submit" (perform this.save)}}> <form {{on "submit" (perform this.save)}}>
<fieldset class="is-shadowless is-marginless is-borderless is-fullwidth" data-test-cluster-config-edit-section>
<h2 class="title is-size-5 has-border-bottom-light page-header">
Cluster Config
</h2>
{{#if @cluster.canSet}}
{{#each @cluster.allFields as |attr|}}
<FormField @attr={{attr}} @model={{@cluster}} @showHelpText={{false}} />
{{/each}}
{{else}}
<EmptyState
class="is-box-shadowless"
@title="You do not have permission to set the cluster config"
@message="Ask your administrator if you think you should have access to:"
>
<code>POST /{{@backend}}/config/cluster</code>
</EmptyState>
{{/if}}
</fieldset>
<fieldset class="box is-shadowless is-marginless is-borderless is-fullwidth" data-test-urls-edit-section> <fieldset class="box is-shadowless is-marginless is-borderless is-fullwidth" data-test-urls-edit-section>
<h2 class="title is-size-5 has-border-bottom-light page-header"> <h2 class="title is-size-5 has-border-bottom-light page-header">
Global URLs Global URLs

View File

@ -13,21 +13,21 @@ import errorMessage from 'vault/utils/error-message';
import type RouterService from '@ember/routing/router-service'; import type RouterService from '@ember/routing/router-service';
import type FlashMessageService from 'vault/services/flash-messages'; import type FlashMessageService from 'vault/services/flash-messages';
import type VersionService from 'vault/services/version'; import type VersionService from 'vault/services/version';
import type PkiCrlModel from 'vault/models/pki/crl'; import type PkiConfigCrlModel from 'vault/models/pki/config/crl';
import type PkiUrlsModel from 'vault/models/pki/urls'; import type PkiConfigUrlsModel from 'vault/models/pki/config/urls';
import type { FormField, TtlEvent } from 'vault/app-types'; import type { FormField, TtlEvent } from 'vault/app-types';
interface Args { interface Args {
crl: PkiCrlModel; crl: PkiConfigCrlModel;
urls: PkiUrlsModel; urls: PkiConfigUrlsModel;
} }
interface PkiCrlTtls { interface PkiConfigCrlTtls {
autoRebuildGracePeriod: string; autoRebuildGracePeriod: string;
expiry: string; expiry: string;
deltaRebuildInterval: string; deltaRebuildInterval: string;
ocspExpiry: string; ocspExpiry: string;
} }
interface PkiCrlBooleans { interface PkiConfigCrlBooleans {
autoRebuild: boolean; autoRebuild: boolean;
enableDelta: boolean; enableDelta: boolean;
disable: boolean; disable: boolean;
@ -69,10 +69,10 @@ export default class PkiConfigurationEditComponent extends Component<Args> {
handleTtl(attr: FormField, e: TtlEvent) { handleTtl(attr: FormField, e: TtlEvent) {
const { enabled, goSafeTimeString } = e; const { enabled, goSafeTimeString } = e;
const ttlAttr = attr.name; const ttlAttr = attr.name;
this.args.crl[ttlAttr as keyof PkiCrlTtls] = goSafeTimeString; this.args.crl[ttlAttr as keyof PkiConfigCrlTtls] = goSafeTimeString;
// expiry and ocspExpiry both correspond to 'disable' booleans // expiry and ocspExpiry both correspond to 'disable' booleans
// so when ttl is enabled, the booleans are set to false // so when ttl is enabled, the booleans are set to false
this.args.crl[attr.options.mapToBoolean as keyof PkiCrlBooleans] = attr.options.isOppositeValue this.args.crl[attr.options.mapToBoolean as keyof PkiConfigCrlBooleans] = attr.options.isOppositeValue
? !enabled ? !enabled
: enabled; : enabled;
} }

View File

@ -11,7 +11,7 @@ import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import errorMessage from 'vault/utils/error-message'; import errorMessage from 'vault/utils/error-message';
import type PkiActionModel from 'vault/models/pki/action'; import type PkiActionModel from 'vault/models/pki/action';
import type PkiUrlsModel from 'vault/models/pki/urls'; import type PkiConfigUrlsModel from 'vault/models/pki/config/urls';
import type FlashMessageService from 'vault/services/flash-messages'; import type FlashMessageService from 'vault/services/flash-messages';
import type RouterService from '@ember/routing/router-service'; import type RouterService from '@ember/routing/router-service';
import type { ValidationMap } from 'vault/vault/app-types'; import type { ValidationMap } from 'vault/vault/app-types';
@ -22,7 +22,7 @@ interface AdapterOptions {
} }
interface Args { interface Args {
model: PkiActionModel; model: PkiActionModel;
urls: PkiUrlsModel; urls: PkiConfigUrlsModel;
onCancel: CallableFunction; onCancel: CallableFunction;
onComplete: CallableFunction; onComplete: CallableFunction;
onSave?: CallableFunction; onSave?: CallableFunction;
@ -107,8 +107,10 @@ export default class PkiGenerateRootComponent extends Component<Args> {
const continueSave = this.checkFormValidity(); const continueSave = this.checkFormValidity();
if (!continueSave) return; if (!continueSave) return;
try { try {
yield this.setUrls();
yield this.args.model.save({ adapterOptions: this.args.adapterOptions }); yield this.args.model.save({ adapterOptions: this.args.adapterOptions });
// root generation must occur first in case templates are used for URL fields
// this way an issuer_id exists for backend to interpolate into the template
yield this.setUrls();
this.flashMessages.success('Successfully generated root.'); this.flashMessages.success('Successfully generated root.');
if (this.args.onSave) { if (this.args.onSave) {
this.args.onSave(); this.args.onSave();

View File

@ -18,7 +18,8 @@ export default class PkiRoute extends Route {
const mountPath = this.secretMountPath.currentPath; const mountPath = this.secretMountPath.currentPath;
return hash({ return hash({
role: this.pathHelp.getNewModel('pki/role', mountPath), role: this.pathHelp.getNewModel('pki/role', mountPath),
urls: this.pathHelp.getNewModel('pki/urls', mountPath), urls: this.pathHelp.getNewModel('pki/config/urls', mountPath),
cluster: this.pathHelp.getNewModel('pki/config/cluster', mountPath),
key: this.pathHelp.getNewModel('pki/key', mountPath), key: this.pathHelp.getNewModel('pki/key', mountPath),
signCsr: this.pathHelp.getNewModel('pki/sign-intermediate', mountPath), signCsr: this.pathHelp.getNewModel('pki/sign-intermediate', mountPath),
certGenerate: this.pathHelp.getNewModel('pki/certificate/generate', mountPath), certGenerate: this.pathHelp.getNewModel('pki/certificate/generate', mountPath),

View File

@ -14,8 +14,9 @@ export default class PkiConfigurationRoute extends Route {
const engine = this.modelFor('application'); const engine = this.modelFor('application');
return hash({ return hash({
engine, engine,
urls: this.store.findRecord('pki/urls', engine.id).catch((e) => e.httpStatus), cluster: this.store.findRecord('pki/config/cluster', engine.id).catch((e) => e.httpStatus),
crl: this.store.findRecord('pki/crl', engine.id).catch((e) => e.httpStatus), urls: this.store.findRecord('pki/config/urls', engine.id).catch((e) => e.httpStatus),
crl: this.store.findRecord('pki/config/crl', engine.id).catch((e) => e.httpStatus),
}); });
} }
} }

View File

@ -12,9 +12,10 @@ export default class PkiConfigurationEditRoute extends Route {
@service secretMountPath; @service secretMountPath;
model() { model() {
const { urls, crl, engine } = this.modelFor('configuration'); const { cluster, urls, crl, engine } = this.modelFor('configuration');
return { return {
engineId: engine.id, engineId: engine.id,
cluster,
urls, urls,
crl, crl,
}; };

View File

@ -20,10 +20,11 @@ export default class ConfigurationIndexRoute extends Route {
} }
model() { model() {
const { urls, crl, engine } = this.modelFor('configuration'); const { cluster, urls, crl, engine } = this.modelFor('configuration');
return hash({ return hash({
hasConfig: this.shouldPromptConfig, hasConfig: this.shouldPromptConfig,
engine, engine,
cluster,
urls, urls,
crl, crl,
mountConfig: this.fetchMountConfig(engine.id), mountConfig: this.fetchMountConfig(engine.id),

View File

@ -10,4 +10,9 @@
</p.levelLeft> </p.levelLeft>
</PageHeader> </PageHeader>
<Page::PkiConfigurationEdit @urls={{this.model.urls}} @crl={{this.model.crl}} @backend={{this.model.engineId}} /> <Page::PkiConfigurationEdit
@cluster={{this.model.cluster}}
@urls={{this.model.urls}}
@crl={{this.model.crl}}
@backend={{this.model.engineId}}
/>

View File

@ -10,10 +10,11 @@
/> />
<Page::PkiConfigurationDetails <Page::PkiConfigurationDetails
@cluster={{this.model.cluster}}
@urls={{this.model.urls}} @urls={{this.model.urls}}
@crl={{this.model.crl}} @crl={{this.model.crl}}
@mountConfig={{this.model.mountConfig}} @mountConfig={{this.model.mountConfig}}
@currentPath={{this.model.engine.id}} @backend={{this.model.engine.id}}
@canDeleteAllIssuers={{this.model.issuerModel.canDeleteAllIssuers}} @canDeleteAllIssuers={{this.model.issuerModel.canDeleteAllIssuers}}
@hasConfig={{this.model.hasConfig}} @hasConfig={{this.model.hasConfig}}
/> />

View File

@ -45,7 +45,7 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
await visit(`/vault/secrets/${this.mountPath}/pki/overview`); await visit(`/vault/secrets/${this.mountPath}/pki/overview`);
await click(SELECTORS.emptyStateLink); await click(SELECTORS.emptyStateLink);
configs = this.store.peekAll('pki/action'); configs = this.store.peekAll('pki/action');
urls = this.store.peekRecord('pki/urls', this.mountPath); urls = this.store.peekRecord('pki/config/urls', this.mountPath);
config = configs.objectAt(0); config = configs.objectAt(0);
assert.strictEqual(configs.length, 1, 'One config model present'); assert.strictEqual(configs.length, 1, 'One config model present');
assert.false(urls.hasDirtyAttributes, 'URLs is loaded from endpoint'); assert.false(urls.hasDirtyAttributes, 'URLs is loaded from endpoint');
@ -54,13 +54,13 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
// Cancel button rolls it back // Cancel button rolls it back
await click(SELECTORS.configuration.cancelButton); await click(SELECTORS.configuration.cancelButton);
configs = this.store.peekAll('pki/action'); configs = this.store.peekAll('pki/action');
urls = this.store.peekRecord('pki/urls', this.mountPath); urls = this.store.peekRecord('pki/config/urls', this.mountPath);
assert.strictEqual(configs.length, 0, 'config model is rolled back on cancel'); assert.strictEqual(configs.length, 0, 'config model is rolled back on cancel');
assert.strictEqual(urls.id, this.mountPath, 'Urls still exists on exit'); assert.strictEqual(urls.id, this.mountPath, 'Urls still exists on exit');
await click(SELECTORS.emptyStateLink); await click(SELECTORS.emptyStateLink);
configs = this.store.peekAll('pki/action'); configs = this.store.peekAll('pki/action');
urls = this.store.peekRecord('pki/urls', this.mountPath); urls = this.store.peekRecord('pki/config/urls', this.mountPath);
config = configs.objectAt(0); config = configs.objectAt(0);
assert.strictEqual(configs.length, 1, 'One config model present'); assert.strictEqual(configs.length, 1, 'One config model present');
assert.false(urls.hasDirtyAttributes, 'URLs is loaded from endpoint'); assert.false(urls.hasDirtyAttributes, 'URLs is loaded from endpoint');
@ -69,7 +69,7 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
// Exit page via link rolls it back // Exit page via link rolls it back
await click(SELECTORS.overviewBreadcrumb); await click(SELECTORS.overviewBreadcrumb);
configs = this.store.peekAll('pki/action'); configs = this.store.peekAll('pki/action');
urls = this.store.peekRecord('pki/urls', this.mountPath); urls = this.store.peekRecord('pki/config/urls', this.mountPath);
assert.strictEqual(configs.length, 0, 'config model is rolled back on cancel'); assert.strictEqual(configs.length, 0, 'config model is rolled back on cancel');
assert.strictEqual(urls.id, this.mountPath, 'Urls still exists on exit'); assert.strictEqual(urls.id, this.mountPath, 'Urls still exists on exit');
}); });

View File

@ -5,6 +5,8 @@
export const SELECTORS = { export const SELECTORS = {
errorBanner: '[data-test-error-banner]', errorBanner: '[data-test-error-banner]',
configEditSection: '[data-test-cluster-config-edit-section]',
configInput: (attr) => `[data-test-input="${attr}"]`,
urlsEditSection: '[data-test-urls-edit-section]', urlsEditSection: '[data-test-urls-edit-section]',
urlFieldInput: (attr) => `[data-test-input="${attr}"] textarea`, urlFieldInput: (attr) => `[data-test-input="${attr}"] textarea`,
urlFieldLabel: (attr) => `[data-test-input="${attr}"] label`, urlFieldLabel: (attr) => `[data-test-input="${attr}"] label`,

View File

@ -24,8 +24,15 @@ module('Integration | Component | Page::PkiConfigurationDetails', function (hook
this.secretMountPath.currentPath = 'pki-test'; this.secretMountPath.currentPath = 'pki-test';
this.store = this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
this.urls = this.store.createRecord('pki/urls', { id: 'pki-test', issuingCertificates: 'example.com' }); this.cluster = this.store.createRecord('pki/config/cluster', {
this.crl = this.store.createRecord('pki/crl', { id: 'pki-test',
path: 'https://pr-a.vault.example.com/v1/ns1/pki-root',
});
this.urls = this.store.createRecord('pki/config/urls', {
id: 'pki-test',
issuingCertificates: 'example.com',
});
this.crl = this.store.createRecord('pki/config/crl', {
id: 'pki-test', id: 'pki-test',
expiry: '20h', expiry: '20h',
disable: false, disable: false,
@ -54,6 +61,17 @@ module('Integration | Component | Page::PkiConfigurationDetails', function (hook
}; };
}); });
test('shows the correct information on cluster config', async function (assert) {
await render(hbs`<Page::PkiConfigurationDetails @cluster={{this.cluster}} @hasConfig={{true}} />,`, {
owner: this.engine,
});
assert
.dom(SELECTORS.rowValue("Mount's API path"))
.hasText('https://pr-a.vault.example.com/v1/ns1/pki-root', 'mount API path row renders');
assert.dom(SELECTORS.rowValue('AIA path')).hasText('None', "renders 'None' when no data");
});
test('shows the correct information on global urls section', async function (assert) { test('shows the correct information on global urls section', async function (assert) {
await render( await render(
hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} @hasConfig={{true}} />,`, hbs`<Page::PkiConfigurationDetails @urls={{this.urls}} @crl={{this.crl}} @mountConfig={{this.mountConfig}} @hasConfig={{true}} />,`,

View File

@ -26,8 +26,12 @@ module('Integration | Component | page/pki-configuration-edit', function (hooks)
this.backend = 'pki-engine'; this.backend = 'pki-engine';
// both models only use findRecord. API parameters for pki/crl // both models only use findRecord. API parameters for pki/crl
// are set by default backend values when the engine is mounted // are set by default backend values when the engine is mounted
this.store.pushPayload('pki/crl', { this.store.pushPayload('pki/config/cluster', {
modelName: 'pki/crl', modelName: 'pki/config/cluster',
id: this.backend,
});
this.store.pushPayload('pki/config/crl', {
modelName: 'pki/config/crl',
id: this.backend, id: this.backend,
auto_rebuild: false, auto_rebuild: false,
auto_rebuild_grace_period: '12h', auto_rebuild_grace_period: '12h',
@ -38,19 +42,31 @@ module('Integration | Component | page/pki-configuration-edit', function (hooks)
ocsp_disable: false, ocsp_disable: false,
ocsp_expiry: '12h', ocsp_expiry: '12h',
}); });
this.store.pushPayload('pki/urls', { this.store.pushPayload('pki/config/urls', {
modelName: 'pki/urls', modelName: 'pki/config/urls',
id: this.backend, id: this.backend,
issuing_certificates: ['hashicorp.com'], issuing_certificates: ['hashicorp.com'],
crl_distribution_points: ['some-crl-distribution.com'], crl_distribution_points: ['some-crl-distribution.com'],
ocsp_servers: ['ocsp-stuff.com'], ocsp_servers: ['ocsp-stuff.com'],
}); });
this.urls = this.store.peekRecord('pki/urls', this.backend); this.cluster = this.store.peekRecord('pki/config/cluster', this.backend);
this.crl = this.store.peekRecord('pki/crl', this.backend); this.crl = this.store.peekRecord('pki/config/crl', this.backend);
this.urls = this.store.peekRecord('pki/config/urls', this.backend);
}); });
test('it renders with config data and updates config', async function (assert) { test('it renders with config data and updates config', async function (assert) {
assert.expect(27); assert.expect(28);
this.server.post(`/${this.backend}/config/cluster`, (schema, req) => {
assert.ok(true, 'request made to save cluster config');
assert.propEqual(
JSON.parse(req.requestBody),
{
path: 'https://pr-a.vault.example.com/v1/ns1/pki-root',
aia_path: 'http://another-path.com',
},
'it updates config model attributes'
);
});
this.server.post(`/${this.backend}/config/crl`, (schema, req) => { this.server.post(`/${this.backend}/config/crl`, (schema, req) => {
assert.ok(true, 'request made to save crl config'); assert.ok(true, 'request made to save crl config');
assert.propEqual( assert.propEqual(
@ -83,6 +99,7 @@ module('Integration | Component | page/pki-configuration-edit', function (hooks)
await render( await render(
hbs` hbs`
<Page::PkiConfigurationEdit <Page::PkiConfigurationEdit
@cluster={{this.cluster}}
@urls={{this.urls}} @urls={{this.urls}}
@crl={{this.crl}} @crl={{this.crl}}
@backend={{this.backend}} @backend={{this.backend}}
@ -91,6 +108,7 @@ module('Integration | Component | page/pki-configuration-edit', function (hooks)
this.context this.context
); );
assert.dom(SELECTORS.configEditSection).exists('renders config section');
assert.dom(SELECTORS.urlsEditSection).exists('renders urls section'); assert.dom(SELECTORS.urlsEditSection).exists('renders urls section');
assert.dom(SELECTORS.crlEditSection).exists('renders crl section'); assert.dom(SELECTORS.crlEditSection).exists('renders crl section');
assert.dom(SELECTORS.cancelButton).exists(); assert.dom(SELECTORS.cancelButton).exists();
@ -101,6 +119,8 @@ module('Integration | Component | page/pki-configuration-edit', function (hooks)
assert.dom(SELECTORS.urlFieldInput('crlDistributionPoints')).hasValue('some-crl-distribution.com'); assert.dom(SELECTORS.urlFieldInput('crlDistributionPoints')).hasValue('some-crl-distribution.com');
assert.dom(SELECTORS.urlFieldInput('ocspServers')).hasValue('ocsp-stuff.com'); assert.dom(SELECTORS.urlFieldInput('ocspServers')).hasValue('ocsp-stuff.com');
await fillIn(SELECTORS.configInput('path'), 'https://pr-a.vault.example.com/v1/ns1/pki-root');
await fillIn(SELECTORS.configInput('aiaPath'), 'http://another-path.com');
await fillIn(SELECTORS.urlFieldInput('issuingCertificates'), 'update-hashicorp.com'); await fillIn(SELECTORS.urlFieldInput('issuingCertificates'), 'update-hashicorp.com');
await fillIn(SELECTORS.urlFieldInput('crlDistributionPoints'), 'test-crl.com'); await fillIn(SELECTORS.urlFieldInput('crlDistributionPoints'), 'test-crl.com');
await fillIn(SELECTORS.urlFieldInput('ocspServers'), 'ocsp.com'); await fillIn(SELECTORS.urlFieldInput('ocspServers'), 'ocsp.com');

View File

@ -25,7 +25,7 @@ module('Integration | Component | page/pki-configure-create', function (hooks) {
{ label: 'configure' }, { label: 'configure' },
]; ];
this.config = this.store.createRecord('pki/action'); this.config = this.store.createRecord('pki/action');
this.urls = this.store.createRecord('pki/urls'); this.urls = this.store.createRecord('pki/config/urls');
}); });
test('it renders', async function (assert) { test('it renders', async function (assert) {

View File

@ -23,7 +23,7 @@ module('Integration | Component | pki-generate-root', function (hooks) {
this.store = this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
this.secretMountPath = this.owner.lookup('service:secret-mount-path'); this.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.secretMountPath.currentPath = 'pki-test'; this.secretMountPath.currentPath = 'pki-test';
this.urls = this.store.createRecord('pki/urls', { id: 'pki-test' }); this.urls = this.store.createRecord('pki/config/urls', { id: 'pki-test' });
this.model = this.store.createRecord('pki/action'); this.model = this.store.createRecord('pki/action');
this.onSave = Sinon.spy(); this.onSave = Sinon.spy();
this.onCancel = Sinon.spy(); this.onCancel = Sinon.spy();

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | pki/config', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(async function () {
this.store = this.owner.lookup('service:store');
this.backend = 'pki-engine';
});
const testHelper = (test) => {
test('it should make request to correct endpoint on update', async function (assert) {
assert.expect(1);
this.server.post(`/${this.backend}/config/${this.endpoint}`, () => {
assert.ok(true, `request made to POST config/${this.endpoint} endpoint on update`);
});
this.store.pushPayload(`pki/config/${this.endpoint}`, {
modelName: `pki/config/${this.endpoint}`,
id: this.backend,
});
const model = this.store.peekRecord(`pki/config/${this.endpoint}`, this.backend);
await model.save();
});
test('it should make request to correct endpoint on find', async function (assert) {
assert.expect(1);
this.server.get(`/${this.backend}/config/${this.endpoint}`, () => {
assert.ok(true, `request is made to GET /config/${this.endpoint} endpoint on find`);
return { data: { id: this.backend } };
});
this.store.findRecord(`pki/config/${this.endpoint}`, this.backend);
});
};
module('cluster', function (hooks) {
hooks.beforeEach(async function () {
this.endpoint = 'cluster';
});
testHelper(test);
});
module('urls', function (hooks) {
hooks.beforeEach(async function () {
this.endpoint = 'urls';
});
testHelper(test);
});
module('crl', function (hooks) {
hooks.beforeEach(async function () {
this.endpoint = 'crl';
});
testHelper(test);
});
});

View File

@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | pki/crl', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.backend = 'pki-engine';
});
test('it should make request to correct endpoint on update', async function (assert) {
assert.expect(1);
this.server.post(`/${this.backend}/config/crl`, () => {
assert.ok(true, 'request made to correct endpoint on update');
});
this.store.pushPayload('pki/crl', {
modelName: 'pki/crl',
id: this.backend,
});
const model = this.store.peekRecord('pki/crl', this.backend);
await model.save();
});
test('it should make request to correct endpoint on find', async function (assert) {
assert.expect(1);
this.server.get(`/${this.backend}/config/crl`, () => {
assert.ok(true, 'request is made to correct endpoint on find');
return { data: { id: this.backend } };
});
this.store.findRecord('pki/crl', this.backend);
});
});

View File

@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | pki/urls', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.backend = 'pki-engine';
});
test('it should make request to correct endpoint on update', async function (assert) {
assert.expect(1);
this.server.post(`/${this.backend}/config/urls`, () => {
assert.ok(true, 'request made to correct endpoint on update');
});
this.store.pushPayload('pki/urls', {
modelName: 'pki/urls',
id: this.backend,
});
const model = this.store.peekRecord('pki/urls', this.backend);
await model.save();
});
test('it should make request to correct endpoint on find', async function (assert) {
assert.expect(1);
this.server.get(`/${this.backend}/config/urls`, () => {
assert.ok(true, 'request is made to correct endpoint on find');
return { data: { id: this.backend } };
});
this.store.findRecord('pki/urls', this.backend);
});
});

View File

@ -1,7 +1,7 @@
import Model from '@ember-data/model'; import Model from '@ember-data/model';
import { FormField } from 'vault/app-types'; import { FormField } from 'vault/app-types';
export default class PkiCrlModel extends Model { export default class PkiConfigCrlModel extends Model {
autoRebuild: boolean; autoRebuild: boolean;
autoRebuildGracePeriod: string; autoRebuildGracePeriod: string;
enableDelta: boolean; enableDelta: boolean;

View File

@ -1,6 +1,6 @@
import Model from '@ember-data/model'; import Model from '@ember-data/model';
export default class PkiUrlsModel extends Model { export default class PkiConfigUrlsModel extends Model {
get useOpenAPI(): boolean; get useOpenAPI(): boolean;
getHelpUrl(backendPath: string): string; getHelpUrl(backendPath: string): string;
issuingCertificates: array; issuingCertificates: array;