diff --git a/ui/app/models/pki/certificate/base.js b/ui/app/models/pki/certificate/base.js
index f83858811f90..8564bfa1951f 100644
--- a/ui/app/models/pki/certificate/base.js
+++ b/ui/app/models/pki/certificate/base.js
@@ -87,8 +87,8 @@ export default class PkiCertificateBaseModel extends Model {
@attr('string', { masked: true }) certificate;
@attr('number') expiration;
@attr('string', { label: 'Issuing CA', masked: true }) issuingCa;
- @attr('string') privateKey; // only returned for type=exported
- @attr('string') privateKeyType; // only returned for type=exported
+ @attr('string', { masked: true }) privateKey; // only returned for type=exported and /issue
+ @attr('string') privateKeyType; // only returned for type=exported and /issue
@attr('number', { formatDate: true }) revocationTime;
@attr('string') serialNumber;
diff --git a/ui/app/models/pki/certificate/generate.js b/ui/app/models/pki/certificate/generate.js
index d5f7e30d8b5c..cdccc2cd403f 100644
--- a/ui/app/models/pki/certificate/generate.js
+++ b/ui/app/models/pki/certificate/generate.js
@@ -21,7 +21,18 @@ const generateFromRole = [
],
},
];
-@withFormFields(null, generateFromRole)
+// Extra fields returned on the /issue endpoint
+const certDisplayFields = [
+ 'certificate',
+ 'commonName',
+ 'revocationTime',
+ 'serialNumber',
+ 'caChain',
+ 'issuingCa',
+ 'privateKey',
+ 'privateKeyType',
+];
+@withFormFields(certDisplayFields, generateFromRole)
export default class PkiCertificateGenerateModel extends PkiCertificateBaseModel {
getHelpUrl(backend) {
return `/v1/${backend}/issue/example?help=1`;
diff --git a/ui/lib/pki/addon/components/page/pki-certificate-details.hbs b/ui/lib/pki/addon/components/page/pki-certificate-details.hbs
index ba79e7ad610e..4fb1d45c4046 100644
--- a/ui/lib/pki/addon/components/page/pki-certificate-details.hbs
+++ b/ui/lib/pki/addon/components/page/pki-certificate-details.hbs
@@ -18,10 +18,28 @@
+{{#if @model.privateKey}}
+
+
+ Next steps
+
+ The
+ private_key
+ is only available once. Make sure you copy and save it now.
+
+
+
+{{/if}}
+
{{#each @model.formFields as |field|}}
- {{#if (eq field.name "certificate")}}
-
-
+ {{#if field.options.masked}}
+
+
{{else if (eq field.name "serialNumber")}}
diff --git a/ui/tests/integration/components/pki/page/pki-certificate-details-test.js b/ui/tests/integration/components/pki/page/pki-certificate-details-test.js
index 482e21b81656..6f288b1b83d8 100644
--- a/ui/tests/integration/components/pki/page/pki-certificate-details-test.js
+++ b/ui/tests/integration/components/pki/page/pki-certificate-details-test.js
@@ -40,7 +40,25 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
},
},
});
+ store.pushPayload('pki/certificate/generate', {
+ modelName: 'pki/certificate/generate',
+ data: {
+ certificate: '-----BEGIN CERTIFICATE-----',
+ ca_chain: '-----BEGIN CERTIFICATE-----',
+ issuer_ca: '-----BEGIN CERTIFICATE-----',
+ private_key: '-----BEGIN PRIVATE KEY-----',
+ private_key_type: 'rsa',
+ common_name: 'example.com Intermediate Authority',
+ issue_date: 1673540867000,
+ serial_number: id,
+ parsed_certificate: {
+ not_valid_after: 1831220897000,
+ not_valid_before: 1673540867000,
+ },
+ },
+ });
this.model = store.peekRecord('pki/certificate/base', id);
+ this.generatedModel = store.peekRecord('pki/certificate/generate', id);
this.server.post('/sys/capabilities-self', () => ({
data: {
@@ -50,7 +68,7 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
}));
});
- test('it should render actions and fields', async function (assert) {
+ test('it should render actions and fields for base cert', async function (assert) {
assert.expect(6);
this.server.post('/pki/revoke', (schema, req) => {
@@ -90,6 +108,56 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
assert.dom('[data-test-value-div="Revocation time"]').exists('Revocation time is displayed');
});
+ test('it should render actions and fields for generated cert', async function (assert) {
+ assert.expect(10);
+
+ this.server.post('/pki/revoke', (schema, req) => {
+ const data = JSON.parse(req.requestBody);
+ assert.strictEqual(
+ data.serial_number,
+ this.model.serialNumber,
+ 'Revoke request made with serial number'
+ );
+ return {
+ data: {
+ revocation_time: 1673972804,
+ revocation_time_rfc3339: '2023-01-17T16:26:44.960933411Z',
+ },
+ };
+ });
+
+ await render(hbs``, { owner: this.engine });
+ assert.dom('[data-test-cert-detail-next-steps]').exists('Private key next steps warning shows');
+ assert
+ .dom('[data-test-component="info-table-row"]')
+ .exists({ count: 9 }, 'Correct number of fields render when certificate has not been revoked');
+ assert
+ .dom('[data-test-value-div="Certificate"] [data-test-masked-input]')
+ .exists('Masked input renders for certificate');
+ assert.dom('[data-test-value-div="Serial number"] code').exists('Serial number renders as monospace');
+ assert
+ .dom('[data-test-value-div="CA Chain"] [data-test-masked-input]')
+ .exists('CA Chain shows with masked value');
+ assert
+ .dom('[data-test-value-div="Issuing CA"] [data-test-masked-input]')
+ .exists('Issuing CA shows with masked value');
+ assert
+ .dom('[data-test-value-div="Private key"] [data-test-masked-input]')
+ .exists('Private key shows with masked value');
+
+ await click('[data-test-pki-cert-download-button]');
+ const { serialNumber, certificate } = this.model;
+ assert.ok(
+ this.downloadSpy.calledWith(serialNumber.replace(/(\s|:)+/g, '-'), certificate),
+ 'Download pem method called with correct args'
+ );
+
+ await click('[data-test-confirm-action-trigger]');
+ await click('[data-test-confirm-button]');
+
+ assert.dom('[data-test-value-div="Revocation time"]').exists('Revocation time is displayed');
+ });
+
test('it should render back button', async function (assert) {
assert.expect(1);