Display CertificateCard instead of MaskedInput for certificates in PKI (#22160)

* replaced each instance of MaskedInput in PKI with CertificateCard

* modify tests for pki-generate-csr

* add test for pki-issuer-details. modify test for pki-certificate-details

* added test for pki-key-details. modified test for pki-sign-intermediate-form

* update 2 test helper files and modify test for pki-issuer-rotate-root

* update test for certificate-card-test.js, update test for the kubernetes configuration-test.js

* modify pki-action-forms-test.js to no longer look for masked input. expand test for pki-issuer-details-test.js to check for all issuer details

* change CertificateCard to show different format types (PEM, DER, nothing) depending on the value provided. update 2 test files to account for this.

* change CertificateCard arg name from @certficateValue to @data to be more inclusive of different uses of CertificateCard (i.e when used for a private key, not a certificate). add description to certificate-card.js

* change naming for attr.options.masked to attr.options.displayCard to reflect the change from MaskedInput to CertificateCard

* add changelog

* change attribute to isCertificate to better fit the title of the component CertificateCard. edit pki-certificate-details.hbs to get rid of extraneous code
This commit is contained in:
malinac02
2023-08-10 16:48:48 -07:00
committed by GitHub
parent ba215dbc12
commit bfe89a40da
27 changed files with 219 additions and 109 deletions

3
changelog/22160.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: display CertificateCard instead of MaskedInput for certificates in PKI
```

View File

@@ -61,13 +61,13 @@ export default class PkiActionModel extends Model {
@attr importedIssuers;
@attr importedKeys;
@attr mapping;
@attr('string', { readOnly: true, masked: true }) certificate;
@attr('string', { readOnly: true, isCertificate: true }) certificate;
/* actionType generate-root */
// readonly attrs returned right after root generation
@attr serialNumber;
@attr('string', { label: 'Issuing CA', readOnly: true, masked: true }) issuingCa;
@attr('string', { label: 'Issuing CA', readOnly: true, isCertificate: true }) issuingCa;
// end of readonly
@attr('string', {
@@ -212,10 +212,10 @@ export default class PkiActionModel extends Model {
@attr('string', { label: 'Issuer ID', readOnly: true, detailLinkTo: 'issuers.issuer.details' }) issuerId; // returned from generate-root action
// For generating and signing a CSR
@attr('string', { label: 'CSR', masked: true }) csr;
@attr('string', { label: 'CSR', isCertificate: true }) csr;
@attr caChain;
@attr('string', { label: 'Key ID', detailLinkTo: 'keys.key.details' }) keyId;
@attr('string', { masked: true }) privateKey;
@attr('string', { isCertificate: true }) privateKey;
@attr('string') privateKeyType;
get backend() {

View File

@@ -83,11 +83,11 @@ export default class PkiCertificateBaseModel extends Model {
otherSans;
// Attrs that come back from API POST request
@attr({ label: 'CA Chain', masked: true }) caChain;
@attr('string', { masked: true }) certificate;
@attr({ label: 'CA Chain', isCertificate: true }) caChain;
@attr('string', { isCertificate: true }) certificate;
@attr('number') expiration;
@attr('string', { label: 'Issuing CA', masked: true }) issuingCa;
@attr('string', { masked: true }) privateKey; // only returned for type=exported and /issue
@attr('string', { label: 'Issuing CA', isCertificate: true }) issuingCa;
@attr('string', { isCertificate: 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;

View File

@@ -42,8 +42,8 @@ export default class PkiIssuerModel extends Model {
@attr isDefault;
@attr('string', { label: 'Issuer ID', detailLinkTo: 'issuers.issuer.details' }) issuerId;
@attr('string', { label: 'Default key ID', detailLinkTo: 'keys.key.details' }) keyId;
@attr({ label: 'CA Chain', masked: true }) caChain;
@attr({ masked: true }) certificate;
@attr({ label: 'CA Chain', isCertificate: true }) caChain;
@attr({ isCertificate: true }) certificate;
@attr('string') serialNumber;
// parsed from certificate contents in serializer (see parse-pki-cert.js)

View File

@@ -2,23 +2,24 @@
@level="mid"
@hasBorder={{true}}
class="is-flex-row has-top-padding-m has-bottom-padding-m is-medium-width"
data-test-certificate-card
>
<span class="has-left-margin-s">
<Icon @name="certificate" @size="24" data-test-certificate-icon />
</span>
<div class="has-left-margin-m is-min-width-0 is-flex-1">
<p class="has-text-weight-bold" data-test-certificate-label>
PEM Format
{{this.format}}
</p>
<code class="is-size-8 truncate-second-line has-text-grey" data-test-certificate-value>
{{@certificateValue}}
{{@data}}
</code>
</div>
<div class="is-flex has-background-white-bis has-side-padding-s has-top-bottom-margin-negative-m">
<CopyButton
data-test-certificate-copy
data-test-copy-button
class="button is-transparent is-flex-v-centered"
@clipboardText={{@certificateValue}}
@clipboardText={{@data}}
@buttonType="button"
@success={{action (set-flash-message "Certificate copied")}}
>

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Component from '@glimmer/component';
/**
* @module CertificateCard
* The CertificateCard component receives data and optionally receives a boolean declaring if that data is meant to be in PEM
* Format. It renders using the <HDS::Card::Container>. To the left there is a certificate icon. In the center there is a label
* which says which format (PEM or DER) the data is in. Below the label is the truncated data. To the right there is a copy
* button to copy the data.
*
* @example
* ```js
* <CertificateCard @data={{value}} @isPem={{true}} />
* ```
* @param {string} data - the data to be displayed in the component (usually in PEM or DER format)
* @param {boolean} isPem - optional argument for if the data is required to be in PEM format (and should thus have the PEM Format label)
*/
export default class CertificateCardComponent extends Component {
// Returns the format the data is in: PEM, DER, or no format if no data is provided
get format() {
if (!this.args.data) return '';
let value;
if (typeof this.args.data === 'object') {
value = this.args.data[0];
} else {
value = this.args.data;
}
if (value.substring(0, 11) === '-----BEGIN ' || this.args.isPem === true) {
return 'PEM Format';
}
return 'DER Format';
}
}

View File

@@ -9,7 +9,7 @@
<InfoTableRow @label="Kubernetes host" @value={{@config.kubernetesHost}} />
{{#if @config.kubernetesCaCert}}
<InfoTableRow @label="Certificate">
<CertificateCard @certificateValue={{@config.kubernetesCaCert}} />
<CertificateCard @data={{@config.kubernetesCaCert}} @isPem={{true}} />
</InfoTableRow>
{{/if}}
{{else}}

View File

@@ -32,9 +32,9 @@
{{/if}}
{{#each @model.formFields as |field|}}
{{#if field.options.masked}}
{{#if field.options.isCertificate}}
<InfoTableRow @label={{or field.options.label (capitalize (humanize (dasherize field.name)))}}>
<MaskedInput @value={{or (get @model field.name) null}} @displayOnly={{true}} @allowCopy={{true}} />
<CertificateCard @data={{get @model field.name}} />
</InfoTableRow>
{{else if (eq field.name "serialNumber")}}
<InfoTableRow @label="Serial number">

View File

@@ -94,14 +94,9 @@
</h2>
{{/if}}
{{#each fields as |attr|}}
{{#if attr.options.masked}}
{{#if attr.options.isCertificate}}
<InfoTableRow @label={{or attr.options.label (humanize (dasherize attr.name))}} @value={{get @issuer attr.name}}>
<MaskedInput
@name={{or attr.options.label (humanize (dasherize attr.name))}}
@value={{get @issuer attr.name}}
@displayOnly={{true}}
@allowCopy={{true}}
/>
<CertificateCard @data={{get @issuer attr.name}} />
</InfoTableRow>
{{else if (eq attr.name "keyId")}}
<InfoTableRow @label={{or attr.options.label (humanize (dasherize attr.name))}} @value={{get @issuer attr.name}}>

View File

@@ -49,7 +49,7 @@
{{/each}}
{{#if @key.privateKey}}
<InfoTableRow @label="Private key">
<MaskedInput @value={{@key.privateKey}} @name="Private key" @displayOnly={{true}} @allowCopy={{true}} />
<CertificateCard @data={{@key.privateKey}} />
</InfoTableRow>
{{/if}}
</div>

View File

@@ -21,8 +21,8 @@
@value={{value}}
@addCopyButton={{eq attr.name "keyId"}}
>
{{#if (and attr.options.masked value)}}
<MaskedInput @value={{value}} @displayOnly={{true}} @allowCopy={{true}} />
{{#if (and attr.options.isCertificate value)}}
<CertificateCard @data={{value}} />
{{else if (eq attr.name "keyId")}}
<LinkTo @route="keys.key.details" @model={{@model.keyId}}>
{{@model.keyId}}

View File

@@ -11,11 +11,11 @@
>
<LinkTo @route={{attr.options.detailLinkTo}} @model={{get @model attr.name}}>{{get @model attr.name}}</LinkTo>
</InfoTableRow>
{{else if attr.options.masked}}
{{else if attr.options.isCertificate}}
<InfoTableRow
@label={{capitalize (or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name)))}}
>
<MaskedInput @value={{get @model attr.name}} @displayOnly={{true}} @allowCopy={{true}} />
<CertificateCard @data={{get @model attr.name}} />
</InfoTableRow>
{{else}}
<InfoTableRow
@@ -27,7 +27,7 @@
{{/each}}
<InfoTableRow @label="Private key">
{{#if @model.privateKey}}
<MaskedInput @value={{@model.privateKey}} @displayOnly={{true}} @allowCopy={{true}} />
<CertificateCard @data={{@model.privateKey}} />
{{else}}
<span class="tag">internal</span>
{{/if}}

View File

@@ -8,8 +8,8 @@
@value={{value}}
@addCopyButton={{or (eq attr.name "issuerId") (eq attr.name "keyId")}}
>
{{#if (and attr.options.masked value)}}
<MaskedInput @value={{value}} @displayOnly={{true}} @allowCopy={{true}} />
{{#if (and attr.options.isCertificate value)}}
<CertificateCard @data={{value}} />
{{else if attr.options.detailLinkTo}}
<LinkTo @route={{attr.options.detailLinkTo}} @model={{value}}>{{value}}</LinkTo>
{{else if (or (eq attr.name "privateKey") (eq attr.name "privateKeyType"))}}

View File

@@ -12,8 +12,8 @@
{{#each this.showFields as |fieldName|}}
{{#let (find-by "name" fieldName @model.allFields) as |attr|}}
<InfoTableRow @label={{or attr.options.label (humanize (dasherize attr.name))}} @value={{get @model attr.name}}>
{{#if (and attr.options.masked (get @model attr.name))}}
<MaskedInput @value={{get @model attr.name}} @displayOnly={{true}} @allowCopy={{true}} />
{{#if (and attr.options.isCertificate (get @model attr.name))}}
<CertificateCard @data={{get @model attr.name}} />
{{else if (eq attr.name "serialNumber")}}
<LinkTo
@route="certificates.certificate.details"

View File

@@ -200,7 +200,7 @@ module('Acceptance | pki action forms test', function (hooks) {
assert.dom(S.configuration.title).hasText('View Root Certificate');
assert.dom(S.configuration.nextStepsBanner).doesNotExist('no private key warning');
assert.dom(S.configuration.title).hasText('View Root Certificate', 'Updates title on page');
assert.dom(S.configuration.saved.certificate).hasClass('allow-copy', 'copyable certificate is masked');
assert.dom(S.configuration.saved.certificate).exists('Copyable certificate exists');
assert.dom(S.configuration.saved.issuerName).hasText(issuerName);
assert.dom(S.configuration.saved.issuerLink).exists('Issuer link exists');
assert.dom(S.configuration.saved.keyLink).exists('Key link exists');
@@ -230,17 +230,13 @@ module('Acceptance | pki action forms test', function (hooks) {
.dom(S.configuration.nextStepsBanner)
.hasText('Next steps The private_key is only available once. Make sure you copy and save it now.');
assert.dom(S.configuration.title).hasText('View Root Certificate', 'Updates title on page');
assert
.dom(S.configuration.saved.certificate)
.hasClass('allow-copy', 'copyable masked certificate exists');
assert.dom(S.configuration.saved.certificate).exists('Copyable certificate exists');
assert
.dom(S.configuration.saved.issuerName)
.doesNotExist('Issuer name not shown because it was not named');
assert.dom(S.configuration.saved.issuerLink).exists('Issuer link exists');
assert.dom(S.configuration.saved.keyLink).exists('Key link exists');
assert
.dom(S.configuration.saved.privateKey)
.hasClass('allow-copy', 'copyable masked private key exists');
assert.dom(S.configuration.saved.privateKey).exists('Copyable private key exists');
assert.dom(S.configuration.saved.keyName).doesNotExist('Key name not shown because it was not named');
assert.dom('[data-test-done]').exists('Done button exists');
// Check that linked issuer has correct common name
@@ -283,9 +279,7 @@ module('Acceptance | pki action forms test', function (hooks) {
.hasText(
'Next steps Copy the CSR below for a parent issuer to sign and then import the signed certificate back into this mount. The private_key is only available once. Make sure you copy and save it now.'
);
assert
.dom(S.configuration.saved.privateKey)
.hasClass('allow-copy', 'copyable masked private key exists');
assert.dom(S.configuration.saved.privateKey).exists('Copyable private key exists');
await click('[data-test-done]');
assert.strictEqual(
currentURL(),

View File

@@ -4,14 +4,14 @@
*/
export const SELECTORS = {
caChain: '[data-test-value-div="CA chain"] [data-test-masked-input]',
certificate: '[data-test-value-div="Certificate"] [data-test-masked-input]',
caChain: '[data-test-value-div="CA chain"] [data-test-certificate-card]',
certificate: '[data-test-value-div="Certificate"] [data-test-certificate-card]',
commonName: '[data-test-row-value="Common name"]',
csr: '[data-test-value-div="CSR"] [data-test-masked-input]',
csr: '[data-test-value-div="CSR"] [data-test-certificate-card]',
expiryDate: '[data-test-row-value="Expiration date"]',
issueDate: '[data-test-row-value="Issue date"]',
issuingCa: '[data-test-value-div="Issuing CA"] [data-test-masked-input]',
privateKey: '[data-test-value-div="Private key"] [data-test-masked-input]',
issuingCa: '[data-test-value-div="Issuing CA"] [data-test-certificate-card]',
privateKey: '[data-test-value-div="Private key"] [data-test-certificate-card]',
revocationTime: '[data-test-row-value="Revocation time"]',
serialNumber: '[data-test-row-value="Serial number"]',
};

View File

@@ -23,13 +23,13 @@ export const SELECTORS = {
urlField: '[data-test-urls-section] [data-test-input]',
// Shown values after save
saved: {
certificate: '[data-test-value-div="Certificate"] [data-test-masked-input]',
certificate: '[data-test-value-div="Certificate"] [data-test-certificate-card]',
commonName: '[data-test-row-value="Common name"]',
issuerName: '[data-test-row-value="Issuer name"]',
issuerLink: '[data-test-value-div="Issuer ID"] a',
keyName: '[data-test-row-value="Key name"]',
keyLink: '[data-test-value-div="Key ID"] a',
privateKey: '[data-test-value-div="Private key"] [data-test-masked-input]',
privateKey: '[data-test-value-div="Private key"] [data-test-certificate-card]',
serialNumber: '[data-test-row-value="Serial number"]',
},
};

View File

@@ -5,6 +5,7 @@
export const SELECTORS = {
configure: '[data-test-pki-issuer-configure]',
copyButtonByName: (name) => `[data-test-value-div="${name}"] [data-test-copy-button]`,
crossSign: '[data-test-pki-issuer-cross-sign]',
defaultGroup: '[data-test-details-group="default"]',
download: '[data-test-issuer-download]',

View File

@@ -26,6 +26,25 @@ o8I9DD+uBHknwByRLXSDmgggwgOYsyTg/IfYoHlLHDD3CaOpkCvUCZvM9bI7nrlx
DU3c2oZTc0mPYGft6U8mVwLqfYTcEduGidTLAQPE5w==
-----END CERTIFICATE-----`;
export const rootDer = `MIIDJjCCAg6gAwIBAgIUZwx170kTAaGFKeyiG3Di
GpwhKvcwDQYJKoZIhvcNAQELBQAwETEPMA0GA1UEAxMGMTExMTExMB4XDTIzMDgw
OTIxMzk0NloXDTIzMDkxMDIxNDAxNlowETEPMA0GA1UEAxMGMTExMTExMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Hm1gjKWDdXuRLZIk3dDabbzlH+Y
2e4rklkMGlrnNqju2+7iIGZa2q8rQ4jEZ3sesSsqGHUEJ2sIG5HnRhl5yawCr9NS
uJP+3zsNueQLQDj6tEnuN0STZQuEJKc+yeept8JGAD0SGnB+THGUYf3if0D8sDT1
nHj3XihtnTG3fN62iKyx2Y95WKrVmT1MnpGjbp4HkRvrHSR8PKyq9Q6YyZkIYbfW
DH3adq6gmiJITzozaUT6efftPOVPr5LLTPKAl3BAmoc8ypM/H1IPaE1Z7ef9lV9w
gazvoJZEsc59hskTWF3ZLcWIxAjcq7u6IX2+dU/A0DmCY6GKmmcZ9W5A9wIDAQAB
o3YwdDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
b2qEtlDZl/ws00ftFQJX6bjoOckwHwYDVR0jBBgwFoAUb2qEtlDZl/ws00ftFQJX
6bjoOckwEQYDVR0RBAowCIIGMTExMTExMA0GCSqGSIb3DQEBCwUAA4IBAQAFI1H8
EOw+YcequlJp1ucCpTRArLUhH0t+l7hQAqwORGQevEP6Ml63dRrZCcke7esrpnL9
7ijKw/PjgoyrM4QS3wAYm8nDm7cZH+f//A2X6WFnvozwKdmDRkacEjMOAe/XU+qh
jdtiETEnUGVH65ulyimKitU5SHV0GNfToKnU/SFBks0bQvglIii0YwgHvSoW1++7
arCjfZqWLdRe7MHfrLpLr4gaebfxSrZfn3utgm+DsJVba3B9JnOZO+yzTiEw6UkJ
rcmZDy0x1/OaCcYHKai4RegsiQ0QrIEI+iC1N6U0PGiGf/V23eoTR0+5H6qngDz2
GzXrbHFAPQbtweCf`;
export const issuerPemBundle = `
-----BEGIN CERTIFICATE-----
MIIDRTCCAi2gAwIBAgIUdKagCL6TnN5xLkwhPbNY8JEcY0YwDQYJKoZIhvcNAQEL

View File

@@ -7,51 +7,58 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { rootPem } from 'vault/tests/helpers/pki/values';
import { rootDer } from 'vault/tests/helpers/pki/values';
const SELECTORS = {
label: '[data-test-certificate-label]',
value: '[data-test-certificate-value]',
icon: '[data-test-certificate-icon]',
copyButton: '[data-test-copy-button]',
};
module('Integration | Component | certificate-card', function (hooks) {
setupRenderingTest(hooks);
test('it renders without a certificate value', async function (assert) {
test('it renders', async function (assert) {
await render(hbs`<CertificateCard />`);
assert.dom(SELECTORS.label).hasText('PEM Format', 'The label text is correct');
assert.dom(SELECTORS.value).hasNoText('The is no value for the certificate');
assert.dom(SELECTORS.label).hasNoText('There is no label because there is no value');
assert.dom(SELECTORS.value).hasNoText('There is no value because none was provided');
assert.dom(SELECTORS.icon).exists('The certificate icon exists');
assert.dom(SELECTORS.copyButton).exists('The copy button exists');
});
test('it renders with a small example value for certificate ', async function (assert) {
await render(hbs`<CertificateCard @certificateValue="test"/>`);
assert.dom(SELECTORS.label).hasText('PEM Format', 'The label text is correct');
assert.dom(SELECTORS.value).hasText('test', 'The value for the certificate is correct');
});
test('it renders with an example Kubernetes CA Certificate', async function (assert) {
const certificate = `
-----BEGIN CERTIFICATE-----
MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD
TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu
ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j
V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj
gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA
FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE
CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS
BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE
BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju
Wm7DCfrPNGVwFWUQOmsPue9rZBgO
-----END CERTIFICATE-----
`;
test('it renders with an example PEM Certificate', async function (assert) {
const certificate = rootPem;
this.set('certificate', certificate);
await render(hbs`<CertificateCard @certificateValue={{this.certificate}}/>`);
await render(hbs`<CertificateCard @data={{this.certificate}} />`);
assert.dom(SELECTORS.label).hasText('PEM Format', 'The label text is correct');
assert.dom(SELECTORS.value).hasText(certificate, 'The value for the CA Certificate is correct');
assert.dom(SELECTORS.label).hasText('PEM Format', 'The label text is PEM Format');
assert.dom(SELECTORS.value).hasText(certificate, 'The data rendered is correct');
assert.dom(SELECTORS.icon).exists('The certificate icon exists');
assert.dom(SELECTORS.copyButton).exists('The copy button exists');
});
test('it renders with an example DER Certificate', async function (assert) {
const certificate = rootDer;
this.set('certificate', certificate);
await render(hbs`<CertificateCard @data={{this.certificate}} />`);
assert.dom(SELECTORS.label).hasText('DER Format', 'The label text is DER Format');
assert.dom(SELECTORS.value).hasText(certificate, 'The data rendered is correct');
assert.dom(SELECTORS.icon).exists('The certificate icon exists');
assert.dom(SELECTORS.copyButton).exists('The copy button exists');
});
test('it renders with the PEM Format label regardless of the value provided when @isPem is true', async function (assert) {
const certificate = 'example-certificate-text';
this.set('certificate', certificate);
await render(hbs`<CertificateCard @data={{this.certificate}} @isPem={{true}}/>`);
assert.dom(SELECTORS.label).hasText('PEM Format', 'The label text is PEM Format');
assert.dom(SELECTORS.value).hasText(certificate, 'The data rendered is correct');
assert.dom(SELECTORS.icon).exists('The certificate icon exists');
assert.dom(SELECTORS.copyButton).exists('The copy button exists');
});
});

View File

@@ -87,14 +87,18 @@ module('Integration | Component | kubernetes | Page::Configuration', function (h
assert
.dom('[data-test-row-value="Kubernetes host"]')
.hasText(this.config.kubernetesHost, 'Kubernetes host value renders');
assert.dom('[data-test-row-label="Certificate"]').exists('Certificate label renders');
assert.dom('[data-test-certificate-card]').exists('Certificate card component renders');
assert
.dom('[data-test-certificate-icon]')
.hasClass('flight-icon-certificate', 'Certificate card icon renders');
assert.dom('[data-test-certificate-label]').hasText('PEM Format', 'Certificate card label renders');
.hasClass('flight-icon-certificate', 'Certificate icon renders');
assert
.dom('[data-test-certificate-card] [data-test-copy-button]')
.exists('Certificate copy button renders');
assert.dom('[data-test-certificate-label]').hasText('PEM Format', 'Certificate label renders');
assert
.dom('[data-test-certificate-value]')
.hasText(this.config.kubernetesCaCert, 'Certificate card value renders');
assert.dom('[data-test-certificate-copy]').exists('Certificate copy button renders');
.hasText(this.config.kubernetesCaCert, 'Certificate value renders');
});
});

View File

@@ -91,8 +91,8 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
.dom('[data-test-component="info-table-row"]')
.exists({ count: 5 }, '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');
.dom('[data-test-value-div="Certificate"] [data-test-certificate-card]')
.exists('Certificate card renders for certificate');
assert.dom('[data-test-value-div="Serial number"] code').exists('Serial number renders as monospace');
await click('[data-test-pki-cert-download-button]');
@@ -132,18 +132,18 @@ module('Integration | Component | pki | Page::PkiCertificateDetails', function (
.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');
.dom('[data-test-value-div="Certificate"] [data-test-certificate-card]')
.exists('Certificate card 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');
.dom('[data-test-value-div="CA Chain"] [data-test-certificate-card]')
.exists('Certificate card renders for CA Chain');
assert
.dom('[data-test-value-div="Issuing CA"] [data-test-masked-input]')
.exists('Issuing CA shows with masked value');
.dom('[data-test-value-div="Issuing CA"] [data-test-certificate-card]')
.exists('Certificate card renders for Issuing CA');
assert
.dom('[data-test-value-div="Private key"] [data-test-masked-input]')
.exists('Private key shows with masked value');
.dom('[data-test-value-div="Private key"] [data-test-certificate-card]')
.exists('Certificate card renders for private key');
await click('[data-test-pki-cert-download-button]');
const { serialNumber, certificate } = this.model;

View File

@@ -206,7 +206,7 @@ module('Integration | Component | page/pki-issuer-rotate-root', function (hooks)
assert.dom(SELECTORS.infoRowValue('Certificate')).exists();
assert.dom(SELECTORS.infoRowValue('Issuer name')).exists();
assert.dom(SELECTORS.infoRowValue('Issuing CA')).exists();
assert.dom(`${SELECTORS.infoRowValue('Private key')} .masked-input`).hasClass('allow-copy');
assert.dom(SELECTORS.infoRowValue('Private key')).exists();
assert.dom(`${SELECTORS.infoRowValue('Private key type')} span`).hasText('rsa');
assert.dom(SELECTORS.infoRowValue('Serial number')).hasText(this.returnedData.serial_number);
assert.dom(SELECTORS.infoRowValue('Key ID')).hasText(this.returnedData.key_id);

View File

@@ -75,4 +75,20 @@ module('Integration | Component | pki key details page', function (hooks) {
assert.dom(SELECTORS.keyDeleteButton).doesNotExist('does not render delete button if no permission');
assert.dom(SELECTORS.keyEditLink).doesNotExist('does not render edit button if no permission');
});
test('it renders the private key as a <CertificateCard> component when there is a private key', async function (assert) {
this.model.privateKey = 'private-key-value';
await render(
hbs`
<Page::PkiKeyDetails
@key={{this.model}}
@canDelete={{false}}
@canEdit={{false}}
/>
`,
{ owner: this.engine }
);
assert.dom('[data-test-certificate-card]').exists('Certificate card renders for the private key');
});
});

View File

@@ -113,13 +113,13 @@ module('Integration | Component | pki-generate-csr', function (hooks) {
'renders Next steps alert banner'
);
assert
.dom('[data-test-value-div="CSR"] [data-test-masked-input] button')
.dom('[data-test-value-div="CSR"] [data-test-certificate-card] button')
.hasAttribute('data-clipboard-text', this.model.csr, 'it renders copyable csr');
assert
.dom('[data-test-value-div="Key ID"] button')
.hasAttribute('data-clipboard-text', this.model.keyId, 'it renders copyable key_id');
assert
.dom('[data-test-value-div="Private key"] [data-test-masked-input] button')
.dom('[data-test-value-div="Private key"] [data-test-certificate-card] button')
.hasAttribute('data-clipboard-text', this.model.privateKey, 'it renders copyable private_key');
assert
.dom('[data-test-value-div="Private key type"]')
@@ -144,7 +144,7 @@ module('Integration | Component | pki-generate-csr', function (hooks) {
'renders Next steps alert banner'
);
assert
.dom('[data-test-value-div="CSR"] [data-test-masked-input] button')
.dom('[data-test-value-div="CSR"] [data-test-certificate-card] button')
.hasAttribute('data-clipboard-text', this.model.csr, 'it renders copyable csr');
assert
.dom('[data-test-value-div="Key ID"] button')

View File

@@ -81,6 +81,36 @@ module('Integration | Component | page/pki-issuer-details', function (hooks) {
assert.dom(SELECTORS.configure).doesNotExist();
});
test('it renders correct details by default', async function (assert) {
await render(
hbs`
<Page::PkiIssuerDetails @issuer={{this.issuer}} />
<div id="modal-wormhole"></div>
`,
this.context
);
// Default group details:
assert.dom(SELECTORS.defaultGroup).exists('Default group of details exists');
assert.dom(SELECTORS.valueByName('Certificate')).exists('Certificate detail exists');
assert.dom(SELECTORS.copyButtonByName('Certificate')).exists('Certificate is copyable');
assert.dom(SELECTORS.valueByName('CA Chain')).exists('CA Chain detail exists');
assert.dom(SELECTORS.copyButtonByName('CA Chain')).exists('CA Chain is copyable');
assert.dom(SELECTORS.valueByName('Common name')).exists('Common name detail exists');
assert.dom(SELECTORS.valueByName('Issuer name')).exists('Issuer name detail exists');
assert.dom(SELECTORS.valueByName('Issuer ID')).exists('Issuer ID detail exists');
assert.dom(SELECTORS.copyButtonByName('Issuer ID')).exists('Issuer ID is copyable');
assert.dom(SELECTORS.valueByName('Default key ID')).exists('Default key ID detail exists');
// Issuer URLs group details:
assert.dom(SELECTORS.urlsGroup).exists('Issuer URLs group of details exists');
assert.dom(SELECTORS.valueByName('Issuing certificates')).exists('Issuing certificates detail exists');
assert
.dom(SELECTORS.valueByName('CRL distribution points'))
.exists('CRL distribution points detail exists');
assert.dom(SELECTORS.valueByName('OCSP servers')).exists('OCSP servers detail exists');
});
test('it renders parsing error banner if issuer certificate contains unsupported OIDs', async function (assert) {
this.issuer.parsedCertificate = {
common_name: 'fancy-cert-unsupported-subj-and-ext-oids',

View File

@@ -71,9 +71,9 @@ module('Integration | Component | pki-sign-intermediate-form', function (hooks)
request_id: 'some-id',
data: {
serial_number: '31:52:b9:09:40',
ca_chain: ['-----root pem------'],
issuing_ca: '-----issuing ca------',
certificate: '-----certificate------',
ca_chain: ['-----BEGIN CERTIFICATE-----'],
issuing_ca: '-----BEGIN CERTIFICATE-----',
certificate: '-----BEGIN CERTIFICATE-----',
},
};
});
@@ -86,13 +86,13 @@ module('Integration | Component | pki-sign-intermediate-form', function (hooks)
await click(selectors.saveButton);
[
{ label: 'Serial number' },
{ label: 'CA Chain', masked: true },
{ label: 'Certificate', masked: true },
{ label: 'Issuing CA', masked: true },
].forEach(({ label, masked }) => {
{ label: 'CA Chain', isCertificate: true },
{ label: 'Certificate', isCertificate: true },
{ label: 'Issuing CA', isCertificate: true },
].forEach(({ label, isCertificate }) => {
assert.dom(selectors.rowByName(label)).exists();
if (masked) {
assert.dom(selectors.valueByName(label)).hasText('***********', `${label} is masked`);
if (isCertificate) {
assert.dom(selectors.valueByName(label)).includesText('PEM Format', `${label} is isCertificate`);
} else {
assert.dom(selectors.valueByName(label)).hasText('31:52:b9:09:40', `Renders ${label}`);
assert.dom(`${selectors.valueByName(label)} a`).exists(`${label} is a link`);