/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { click, fillIn, render } from '@ember/test-helpers';
import { setupEngine } from 'ember-engines/test-support';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { Response } from 'miragejs';
import { SELECTORS } from 'vault/tests/helpers/pki/page/pki-configuration-edit';
import sinon from 'sinon';
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
module('Integration | Component | page/pki-configuration-edit', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
setupMirage(hooks);
hooks.beforeEach(async function () {
// test context setup
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
this.context = { owner: this.engine }; // this.engine set by setupEngine
this.store = this.owner.lookup('service:store');
this.router = this.owner.lookup('service:router');
sinon.stub(this.router, 'transitionTo');
// component data setup
this.backend = 'pki-engine';
// both models only use findRecord. API parameters for pki/crl
// are set by default backend values when the engine is mounted
this.store.pushPayload('pki/config/cluster', {
modelName: 'pki/config/cluster',
id: this.backend,
});
this.store.pushPayload('pki/config/acme', {
modelName: 'pki/config/acme',
id: this.backend,
});
this.store.pushPayload('pki/config/crl', {
modelName: 'pki/config/crl',
id: this.backend,
auto_rebuild: false,
auto_rebuild_grace_period: '12h',
delta_rebuild_interval: '15m',
disable: false,
enable_delta: false,
expiry: '72h',
ocsp_disable: false,
ocsp_expiry: '12h',
});
this.store.pushPayload('pki/config/urls', {
modelName: 'pki/config/urls',
id: this.backend,
issuing_certificates: ['hashicorp.com'],
crl_distribution_points: ['some-crl-distribution.com'],
ocsp_servers: ['ocsp-stuff.com'],
});
this.acme = this.store.peekRecord('pki/config/acme', this.backend);
this.cluster = this.store.peekRecord('pki/config/cluster', this.backend);
this.crl = this.store.peekRecord('pki/config/crl', this.backend);
this.urls = this.store.peekRecord('pki/config/urls', this.backend);
});
hooks.afterEach(function () {
this.router.transitionTo.restore();
});
test('it renders with config data and updates config', async function (assert) {
assert.expect(32);
this.server.post(`/${this.backend}/config/acme`, (schema, req) => {
assert.ok(true, 'request made to save acme config');
assert.propEqual(
JSON.parse(req.requestBody),
{
allowed_issuers: ['*'],
allowed_roles: ['my-role'],
dns_resolver: 'some-dns',
eab_policy: 'new-account-required',
enabled: true,
},
'it updates acme config model attributes'
);
});
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 cluster config model attributes'
);
});
this.server.post(`/${this.backend}/config/crl`, (schema, req) => {
assert.ok(true, 'request made to save crl config');
assert.propEqual(
JSON.parse(req.requestBody),
{
auto_rebuild: true,
auto_rebuild_grace_period: '24h',
delta_rebuild_interval: '45m',
disable: false,
enable_delta: true,
expiry: '1152h',
ocsp_disable: false,
ocsp_expiry: '24h',
},
'it updates crl config model attributes'
);
});
this.server.post(`/${this.backend}/config/urls`, (schema, req) => {
assert.ok(true, 'request made to save urls config');
assert.propEqual(
JSON.parse(req.requestBody),
{
crl_distribution_points: ['test-crl.com'],
issuing_certificates: ['update-hashicorp.com'],
ocsp_servers: ['ocsp.com'],
},
'it updates url config model attributes'
);
});
await render(
hbs`
`,
this.context
);
assert.dom(SELECTORS.configEditSection).exists('renders config section');
assert.dom(SELECTORS.urlsEditSection).exists('renders urls section');
assert.dom(SELECTORS.crlEditSection).exists('renders crl section');
assert.dom(SELECTORS.cancelButton).exists();
this.urls.eachAttribute((name) => {
assert.dom(SELECTORS.urlFieldInput(name)).exists(`renders ${name} input`);
});
assert.dom(SELECTORS.urlFieldInput('issuingCertificates')).hasValue('hashicorp.com');
assert.dom(SELECTORS.urlFieldInput('crlDistributionPoints')).hasValue('some-crl-distribution.com');
assert.dom(SELECTORS.urlFieldInput('ocspServers')).hasValue('ocsp-stuff.com');
// cluster config
await fillIn(SELECTORS.configInput('path'), 'https://pr-a.vault.example.com/v1/ns1/pki-root');
await fillIn(SELECTORS.configInput('aiaPath'), 'http://another-path.com');
// acme config;
await click(SELECTORS.configInput('enabled'));
await fillIn(SELECTORS.stringListInput('allowedRoles'), 'my-role');
await fillIn(SELECTORS.stringListInput('allowedIssuers'), '*');
await fillIn(SELECTORS.configInput('eabPolicy'), 'new-account-required');
await fillIn(SELECTORS.configInput('dnsResolver'), 'some-dns');
// urls
await fillIn(SELECTORS.urlFieldInput('issuingCertificates'), 'update-hashicorp.com');
await fillIn(SELECTORS.urlFieldInput('crlDistributionPoints'), 'test-crl.com');
await fillIn(SELECTORS.urlFieldInput('ocspServers'), 'ocsp.com');
// confirm default toggle state and text
this.crl.eachAttribute((name, { options }) => {
if (['expiry', 'ocspExpiry'].includes(name)) {
assert.dom(SELECTORS.crlToggleInput(name)).isChecked(`${name} defaults to toggled on`);
assert.dom(SELECTORS.crlFieldLabel(name)).hasTextContaining(options.label);
assert.dom(SELECTORS.crlFieldLabel(name)).hasTextContaining(options.helperTextEnabled);
}
if (['autoRebuildGracePeriod', 'deltaRebuildInterval'].includes(name)) {
assert.dom(SELECTORS.crlToggleInput(name)).isNotChecked(`${name} defaults off`);
assert.dom(SELECTORS.crlFieldLabel(name)).hasTextContaining(options.labelDisabled);
assert.dom(SELECTORS.crlFieldLabel(name)).hasTextContaining(options.helperTextDisabled);
}
});
// toggle everything on
await click(SELECTORS.crlToggleInput('autoRebuildGracePeriod'));
assert
.dom(SELECTORS.crlFieldLabel('autoRebuildGracePeriod'))
.hasTextContaining(
'Auto-rebuild on Vault will rebuild the CRL in the below grace period before expiration',
'it renders auto rebuild toggled on text'
);
await click(SELECTORS.crlToggleInput('deltaRebuildInterval'));
assert
.dom(SELECTORS.crlFieldLabel('deltaRebuildInterval'))
.hasTextContaining(
'Delta CRL building on Vault will rebuild the delta CRL at the interval below:',
'it renders delta crl build toggled on text'
);
// assert ttl values update model attributes
await fillIn(SELECTORS.crlTtlInput('Expiry'), '48');
await fillIn(SELECTORS.crlTtlInput('Auto-rebuild on'), '24');
await fillIn(SELECTORS.crlTtlInput('Delta CRL building on'), '45');
await fillIn(SELECTORS.crlTtlInput('OCSP responder APIs enabled'), '24');
await click(SELECTORS.saveButton);
});
test('it removes urls and sends false crl values', async function (assert) {
assert.expect(8);
this.server.post(`/${this.backend}/config/acme`, () => {});
this.server.post(`/${this.backend}/config/cluster`, () => {});
this.server.post(`/${this.backend}/config/crl`, (schema, req) => {
assert.ok(true, 'request made to save crl config');
assert.propEqual(
JSON.parse(req.requestBody),
{
auto_rebuild: false,
auto_rebuild_grace_period: '12h',
delta_rebuild_interval: '15m',
disable: true,
enable_delta: false,
expiry: '72h',
ocsp_disable: true,
ocsp_expiry: '12h',
},
'crl payload has correct data'
);
});
this.server.post(`/${this.backend}/config/urls`, (schema, req) => {
assert.ok(true, 'request made to save urls config');
assert.propEqual(
JSON.parse(req.requestBody),
{
crl_distribution_points: [],
issuing_certificates: [],
ocsp_servers: [],
},
'url payload has empty arrays'
);
});
await render(
hbs`
`,
this.context
);
await click(SELECTORS.deleteButton('issuingCertificates'));
await click(SELECTORS.deleteButton('crlDistributionPoints'));
await click(SELECTORS.deleteButton('ocspServers'));
// toggle everything off
await click(SELECTORS.crlToggleInput('expiry'));
assert.dom(SELECTORS.crlFieldLabel('expiry')).hasText('No expiry The CRL will not be built.');
assert
.dom(SELECTORS.crlToggleInput('autoRebuildGracePeriod'))
.doesNotExist('expiry off hides the auto rebuild toggle');
assert
.dom(SELECTORS.crlToggleInput('deltaRebuildInterval'))
.doesNotExist('expiry off hides delta crl toggle');
await click(SELECTORS.crlToggleInput('ocspExpiry'));
assert
.dom(SELECTORS.crlFieldLabel('ocspExpiry'))
.hasTextContaining(
'OCSP responder APIs disabled Requests cannot be made to check if an individual certificate is valid.',
'it renders correct toggled off text'
);
await click(SELECTORS.saveButton);
});
test('it renders enterprise only params', async function (assert) {
assert.expect(6);
this.version = this.owner.lookup('service:version');
this.version.version = '1.13.1+ent';
this.server.post(`/${this.backend}/config/acme`, () => {});
this.server.post(`/${this.backend}/config/cluster`, () => {});
this.server.post(`/${this.backend}/config/crl`, (schema, req) => {
assert.ok(true, 'request made to save crl config');
assert.propEqual(
JSON.parse(req.requestBody),
{
auto_rebuild: false,
auto_rebuild_grace_period: '12h',
delta_rebuild_interval: '15m',
disable: false,
enable_delta: false,
expiry: '72h',
ocsp_disable: false,
ocsp_expiry: '12h',
cross_cluster_revocation: true,
unified_crl: true,
unified_crl_on_existing_paths: true,
},
'crl payload includes enterprise params'
);
});
this.server.post(`/${this.backend}/config/urls`, () => {
assert.ok(true, 'request made to save urls config');
});
await render(
hbs`
`,
this.context
);
assert.dom(SELECTORS.groupHeader('Certificate Revocation List (CRL)')).exists();
assert.dom(SELECTORS.groupHeader('Online Certificate Status Protocol (OCSP)')).exists();
assert.dom(SELECTORS.groupHeader('Unified Revocation')).exists();
await click(SELECTORS.checkboxInput('crossClusterRevocation'));
await click(SELECTORS.checkboxInput('unifiedCrl'));
await click(SELECTORS.checkboxInput('unifiedCrlOnExistingPaths'));
await click(SELECTORS.saveButton);
});
test('it does not render enterprise only params for OSS', async function (assert) {
assert.expect(9);
this.version = this.owner.lookup('service:version');
this.version.version = '1.13.1';
this.server.post(`/${this.backend}/config/acme`, () => {});
this.server.post(`/${this.backend}/config/cluster`, () => {});
this.server.post(`/${this.backend}/config/crl`, (schema, req) => {
assert.ok(true, 'request made to save crl config');
assert.propEqual(
JSON.parse(req.requestBody),
{
auto_rebuild: false,
auto_rebuild_grace_period: '12h',
delta_rebuild_interval: '15m',
disable: false,
enable_delta: false,
expiry: '72h',
ocsp_disable: false,
ocsp_expiry: '12h',
},
'crl payload does not include enterprise params'
);
});
this.server.post(`/${this.backend}/config/urls`, () => {
assert.ok(true, 'request made to save urls config');
});
await render(
hbs`
`,
this.context
);
assert.dom(SELECTORS.checkboxInput('crossClusterRevocation')).doesNotExist();
assert.dom(SELECTORS.checkboxInput('unifiedCrl')).doesNotExist();
assert.dom(SELECTORS.checkboxInput('unifiedCrlOnExistingPaths')).doesNotExist();
assert.dom(SELECTORS.groupHeader('Certificate Revocation List (CRL)')).exists();
assert.dom(SELECTORS.groupHeader('Online Certificate Status Protocol (OCSP)')).exists();
assert.dom(SELECTORS.groupHeader('Unified Revocation')).doesNotExist();
await click(SELECTORS.saveButton);
});
test('it renders empty states if no update capabilities', async function (assert) {
assert.expect(4);
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub(['read']));
await render(
hbs`
`,
this.context
);
assert
.dom(`${SELECTORS.configEditSection} [data-test-component="empty-state"]`)
.hasText(
"You do not have permission to set this mount's the cluster config Ask your administrator if you think you should have access to: POST /pki-engine/config/cluster"
);
assert
.dom(`${SELECTORS.acmeEditSection} [data-test-component="empty-state"]`)
.hasText(
"You do not have permission to set this mount's ACME config Ask your administrator if you think you should have access to: POST /pki-engine/config/acme"
);
assert
.dom(`${SELECTORS.urlsEditSection} [data-test-component="empty-state"]`)
.hasText(
"You do not have permission to set this mount's URLs Ask your administrator if you think you should have access to: POST /pki-engine/config/urls"
);
assert
.dom(`${SELECTORS.crlEditSection} [data-test-component="empty-state"]`)
.hasText(
"You do not have permission to set this mount's revocation configuration Ask your administrator if you think you should have access to: POST /pki-engine/config/crl"
);
});
test('it renders alert banner and endpoint respective error', async function (assert) {
assert.expect(4);
this.server.post(`/${this.backend}/config/acme`, () => {
return new Response(500, {}, { errors: ['something wrong with acme'] });
});
this.server.post(`/${this.backend}/config/cluster`, () => {
return new Response(500, {}, { errors: ['something wrong with cluster'] });
});
this.server.post(`/${this.backend}/config/crl`, () => {
return new Response(500, {}, { errors: ['something wrong with crl'] });
});
this.server.post(`/${this.backend}/config/urls`, () => {
return new Response(500, {}, { errors: ['something wrong with urls'] });
});
await render(
hbs`
`,
this.context
);
await click(SELECTORS.saveButton);
assert
.dom(SELECTORS.errorBanner)
.hasText(
'Error POST config/cluster: something wrong with cluster POST config/acme: something wrong with acme POST config/urls: something wrong with urls POST config/crl: something wrong with crl'
);
assert.dom(`${SELECTORS.errorBanner} ul`).hasClass('bullet');
// change 3 out of 4 requests to be successful to assert single error renders correctly
this.server.post(`/${this.backend}/config/acme`, () => new Response(200));
this.server.post(`/${this.backend}/config/cluster`, () => new Response(200));
this.server.post(`/${this.backend}/config/crl`, () => new Response(200));
await click(SELECTORS.saveButton);
assert.dom(SELECTORS.errorBanner).hasText('Error POST config/urls: something wrong with urls');
assert.dom(`${SELECTORS.errorBanner} ul`).doesNotHaveClass('bullet');
});
});