Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: PKI show missing info on generated certificate #21635

Merged
merged 8 commits into from
Jul 7, 2023
3 changes: 3 additions & 0 deletions changelog/21635.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Adds missing values to details view after generating PKI certificate
```
4 changes: 2 additions & 2 deletions ui/app/models/pki/certificate/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
13 changes: 12 additions & 1 deletion ui/app/models/pki/certificate/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
Expand Down
19 changes: 16 additions & 3 deletions ui/lib/pki/addon/components/page/pki-certificate-details.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,23 @@
</ToolbarActions>
</Toolbar>

{{#if @model.privateKey}}
<div class="has-top-margin-m">
<Hds::Alert data-test-cert-detail-next-steps @type="inline" @color="highlight" class="has-bottom-margin-s" as |A|>
<A.Title>Next steps</A.Title>
<A.Description>
The
<code>private_key</code>
is only available once. Make sure you copy and save it now.
</A.Description>
</Hds::Alert>
</div>
{{/if}}

{{#each @model.formFields as |field|}}
{{#if (eq field.name "certificate")}}
<InfoTableRow @label="Certificate">
<MaskedInput @value={{@model.certificate}} @displayOnly={{true}} @allowCopy={{true}} />
{{#if field.options.masked}}
<InfoTableRow @label={{or field.options.label (capitalize (humanize (dasherize field.name)))}}>
<MaskedInput @value={{or (get @model field.name) null}} @displayOnly={{true}} @allowCopy={{true}} />
</InfoTableRow>
{{else if (eq field.name "serialNumber")}}
<InfoTableRow @label="Serial number">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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`<Page::PkiCertificateDetails @model={{this.generatedModel}} />`, { 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);

Expand Down