Skip to content

Commit

Permalink
UI/ PKI UI Redesign (#12541)
Browse files Browse the repository at this point in the history
* installs node-forge

* correctly displays and formats cert metadata

* removes labels

* uses helper in hbs file

* adds named arg to helper

* pki-ca-cert displays common name, issue & expiry date

* alphabetizes some attrs

* adds test for date helper
  • Loading branch information
hellobontempo committed Oct 4, 2021
1 parent eda96be commit 1a1c1cb
Show file tree
Hide file tree
Showing 17 changed files with 35,465 additions and 48 deletions.
3 changes: 3 additions & 0 deletions changelog/12541.txt
@@ -0,0 +1,3 @@
```release-note:improvement
ui: parse and display pki cert metadata
```
10 changes: 9 additions & 1 deletion ui/app/adapters/pki-ca-certificate.js
@@ -1,3 +1,4 @@
import { parsePkiCert } from '../helpers/parse-pki-cert';
import ApplicationAdapter from './application';

export default ApplicationAdapter.extend({
Expand Down Expand Up @@ -41,7 +42,14 @@ export default ApplicationAdapter.extend({
}
response.id = snapshot.id;
response.modelName = type.modelName;
store.pushPayload(type.modelName, response);
// only parse if certificate is attached to response
if (response.data && response.data.certificate) {
const caCertMetadata = parsePkiCert([response.data]);
const transformedResponse = { ...response, ...caCertMetadata };
store.pushPayload(type.modelName, transformedResponse);
} else {
store.pushPayload(type.modelName, response);
}
});
},

Expand Down
20 changes: 20 additions & 0 deletions ui/app/helpers/parse-pki-cert.js
@@ -0,0 +1,20 @@
import { helper } from '@ember/component/helper';
import { pki } from 'node-forge';

export function parsePkiCert([model]) {
// model has to be the responseJSON from PKI serializer
if (!model.certificate) {
return;
}
const cert = pki.certificateFromPem(model.certificate);
const commonName = cert.subject.getField('CN') ? cert.subject.getField('CN').value : null;
const issueDate = cert.validity.notBefore;
const expiryDate = cert.validity.notAfter;
return {
common_name: commonName,
issue_date: issueDate,
expiry_date: expiryDate,
};
}

export default helper(parsePkiCert);
28 changes: 16 additions & 12 deletions ui/app/models/pki-ca-certificate.js
Expand Up @@ -4,43 +4,49 @@ import { computed } from '@ember/object';
import Certificate from './pki-certificate';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';

// TODO: alphabetize attrs
export default Certificate.extend({
DISPLAY_FIELDS: computed(function() {
return [
'csr',
'certificate',
'expiration',
'commonName',
'issueDate',
'expiryDate',
'issuingCa',
'caChain',
'privateKey',
'privateKeyType',
'serialNumber',
];
}),
addBasicConstraints: attr('boolean', {
label: 'Add a Basic Constraints extension with CA: true',
helpText:
'Only needed as a workaround in some compatibility scenarios with Active Directory Certificate Services',
}),
backend: attr('string', {
readOnly: true,
}),

caType: attr('string', {
possibleValues: ['root', 'intermediate'],
defaultValue: 'root',
label: 'CA Type',
readOnly: true,
}),
uploadPemBundle: attr('boolean', {
label: 'Upload PEM bundle',
readOnly: true,
commonName: attr('string'),
expiryDate: attr('string', {
label: 'Expiration date',
}),
issueDate: attr('string'),
pemBundle: attr('string', {
label: 'PEM bundle',
editType: 'file',
}),
addBasicConstraints: attr('boolean', {
label: 'Add a Basic Constraints extension with CA: true',
helpText:
'Only needed as a workaround in some compatibility scenarios with Active Directory Certificate Services',
uploadPemBundle: attr('boolean', {
label: 'Upload PEM bundle',
readOnly: true,
}),

fieldDefinition: computed('caType', 'uploadPemBundle', function() {
const type = this.caType;
const isUpload = this.uploadPemBundle;
Expand Down Expand Up @@ -92,7 +98,6 @@ export default Certificate.extend({

return groups;
}),

type: attr('string', {
possibleValues: ['internal', 'exported'],
defaultValue: 'internal',
Expand Down Expand Up @@ -145,7 +150,6 @@ export default Certificate.extend({
label: 'CSR',
masked: true,
}),
expiration: attr(),

deletePath: lazyCapabilities(apiPath`${'backend'}/root`, 'backend'),
canDeleteRoot: and('deletePath.canDelete', 'deletePath.canSudo'),
Expand Down
21 changes: 10 additions & 11 deletions ui/app/models/pki-certificate.js
Expand Up @@ -17,27 +17,30 @@ export default Model.extend({
DISPLAY_FIELDS: computed(function() {
return [
'certificate',
'commonName',
'issuingCa',
'caChain',
'privateKey',
'privateKeyType',
'serialNumber',
'revocationTime',
'issueDate',
'expiryDate',
'serialNumber',
];
}),

commonName: attr('string'),
expiryDate: attr('string', {
label: 'Expiration date',
}),
issueDate: attr('string'),
role: attr('object', {
readOnly: true,
}),

revocationTime: attr('number'),
commonName: attr('string', {
label: 'Common Name',
}),

altNames: attr('string', {
label: 'DNS/Email Subject Alternative Names (SANs)',
}),

ipSans: attr('string', {
label: 'IP Subject Alternative Names (SANs)',
}),
Expand All @@ -47,22 +50,18 @@ export default Model.extend({
helpText:
'The format is the same as OpenSSL: <oid>;<type>:<value> where the only current valid type is UTF8',
}),

ttl: attr({
label: 'TTL',
editType: 'ttl',
}),

format: attr('string', {
defaultValue: 'pem',
possibleValues: ['pem', 'der', 'pem_bundle'],
}),

excludeCnFromSans: attr('boolean', {
label: 'Exclude Common Name from Subject Alternative Names (SANs)',
defaultValue: false,
}),

certificate: attr('string', {
masked: true,
}),
Expand Down
10 changes: 9 additions & 1 deletion ui/app/serializers/pki-certificate.js
Expand Up @@ -2,6 +2,7 @@ import RESTSerializer from '@ember-data/serializer/rest';
import { isNone, isBlank } from '@ember/utils';
import { assign } from '@ember/polyfills';
import { decamelize } from '@ember/string';
import { parsePkiCert } from '../helpers/parse-pki-cert';

export default RESTSerializer.extend({
keyForAttribute: function(attr) {
Expand Down Expand Up @@ -41,7 +42,14 @@ export default RESTSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
const responseJSON = this.normalizeItems(payload);
const { modelName } = primaryModelClass;
let transformedPayload = { [modelName]: responseJSON };
let transformedPayload, certMetadata;
// hits cert/list endpoint first which returns an array, only want to parse if response is not an array
if (!Array.isArray(responseJSON)) {
certMetadata = parsePkiCert([responseJSON]);
transformedPayload = { [modelName]: { ...certMetadata, ...responseJSON } };
} else {
transformedPayload = { [modelName]: responseJSON };
}
return this._super(store, primaryModelClass, transformedPayload, id, requestType);
},

Expand Down
2 changes: 2 additions & 0 deletions ui/app/styles/components/info-table-row.scss
Expand Up @@ -23,6 +23,8 @@
.column {
align-self: center;
padding-left: 0px;
padding: $spacing-m;

&.info-table-row-edit {
padding-bottom: 0.3rem;
padding-top: 0.3rem;
Expand Down
35 changes: 21 additions & 14 deletions ui/app/templates/components/config-pki-ca.hbs
Expand Up @@ -10,25 +10,23 @@
{{#if (or model.certificate model.csr)}}
{{#each model.attrs as |attr|}}
{{#if attr.options.masked}}
<InfoTableRow @label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}} @value={{get model attr.name}}>
<InfoTableRow data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}}>
<MaskedInput
@value={{get model attr.name}}
@displayOnly={{true}}
@allowCopy={{true}}
/>
</InfoTableRow>
{{else if (eq attr.name "expiration")}}
{{info-table-row
data-test-table-row
label=(capitalize (or attr.options.label (humanize (dasherize attr.name))))
value=(date-format (get model attr.name) 'MMM dd, yyyy hh:mm:ss a')
}}
{{else if (and (get model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
<InfoTableRow data-test-table-row={{value}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{date-format (get model attr.name) 'MMM dd, yyyy hh:mm:ss a' isFormatted=true}}/>
{{else}}
{{info-table-row
data-test-table-row
label=(capitalize (or attr.options.label (humanize (dasherize attr.name))))
value=(get model attr.name)
}}
<InfoTableRow data-test-table-row={{value}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}}/>
{{/if}}
{{/each}}
<div class="field is-grouped box is-fullwidth is-bottomless">
Expand Down Expand Up @@ -94,15 +92,24 @@
/>
{{#each model.attrs as |attr|}}
{{#if attr.options.masked}}
<InfoTableRow @label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}} @value={{get model attr.name}}>
<InfoTableRow
data-test-table-row={{value}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}}>
<MaskedInput
@value={{get model attr.name}}
@displayOnly={{true}}
@allowCopy={{true}}
/>
</InfoTableRow>
{{else if (and (get model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
<InfoTableRow data-test-table-row={{value}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{date-format (get model attr.name) 'MMM dd, yyyy hh:mm:ss a' isFormatted=true}}/>
{{else}}
{{info-table-row data-test-table-row label=(capitalize (or attr.options.label (humanize (dasherize attr.name)))) value=(get model attr.name)}}
<InfoTableRow data-test-table-row={{value}}
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}}/>
{{/if}}
{{/each}}
<div class="field is-grouped box is-fullwidth is-bottomless">
Expand Down
17 changes: 14 additions & 3 deletions ui/app/templates/components/pki-cert-show.hbs
Expand Up @@ -13,22 +13,33 @@
<MessageError @model={{model}} />
{{#each model.attrs as |attr|}}
{{#if (eq attr.type "object")}}
<InfoTableRow @label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}} @value={{stringify (get model attr.name)}} />
<InfoTableRow data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{stringify (get model attr.name)}} />
{{else}}
{{#if attr.options.masked}}
<InfoTableRow @label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}} @value={{get model attr.name}}>
<InfoTableRow data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}}>
<MaskedInput
@value={{get model attr.name}}
@displayOnly={{true}}
@allowCopy={{true}}
/>
</InfoTableRow>
{{else if (and (get model attr.name) (or (eq attr.name "issueDate") (eq attr.name "expiryDate")))}}
<InfoTableRow data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{date-format (get model attr.name) 'MMM dd, yyyy hh:mm:ss a' isFormatted=true}} />
{{else}}
<InfoTableRow @label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}} @value={{get model attr.name}} />
<InfoTableRow data-test-table-row
@label={{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
@value={{get model attr.name}} />
{{/if}}
{{/if}}
{{/each}}
</div>

<div class="field is-grouped is-grouped-split box is-fullwidth is-bottomless">
<div class="field is-grouped">
<div class="control">
Expand Down
5 changes: 4 additions & 1 deletion ui/lib/core/addon/helpers/date-format.js
@@ -1,8 +1,11 @@
import { helper } from '@ember/component/helper';
import { format, parseISO } from 'date-fns';

export function dateFormat([date, style]) {
export function dateFormat([date, style], { isFormatted = false }) {
// see format breaking in upgrade to date-fns 2.x https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md#changed-5
if (isFormatted) {
return format(new Date(date), style);
}
let number = typeof date === 'string' ? parseISO(date) : date;
if (!number) {
return;
Expand Down

0 comments on commit 1a1c1cb

Please sign in to comment.