diff --git a/changelog/18842.txt b/changelog/18842.txt new file mode 100644 index 000000000..9a69ff66f --- /dev/null +++ b/changelog/18842.txt @@ -0,0 +1,3 @@ +```release-note:feature +**New PKI UI**: Add beta support for new and improved PKI UI +``` \ No newline at end of file diff --git a/ui/app/adapters/pki/action.js b/ui/app/adapters/pki/action.js index 7cf2bfc35..2cb22a502 100644 --- a/ui/app/adapters/pki/action.js +++ b/ui/app/adapters/pki/action.js @@ -24,7 +24,7 @@ export default class PkiActionAdapter extends ApplicationAdapter { ? `${baseUrl}/issuers/generate/intermediate/${type}` : `${baseUrl}/intermediate/generate/${type}`; case 'sign-intermediate': - return `${baseUrl}/issuer/${issuerName}/sign-intermediate`; + return `${baseUrl}/issuer/${encodePath(issuerName)}/sign-intermediate`; default: assert('actionType must be one of import, generate-root, generate-csr or sign-intermediate'); } diff --git a/ui/app/adapters/pki/sign-intermediate.js b/ui/app/adapters/pki/sign-intermediate.js new file mode 100644 index 000000000..15144487b --- /dev/null +++ b/ui/app/adapters/pki/sign-intermediate.js @@ -0,0 +1,19 @@ +import { encodePath } from 'vault/utils/path-encoding-helpers'; +import ApplicationAdapter from '../application'; + +export default class PkiSignIntermediateAdapter extends ApplicationAdapter { + namespace = 'v1'; + + createRecord(store, type, snapshot) { + const serializer = store.serializerFor(type.modelName); + const { backend, issuerRef } = snapshot.record; + const url = `${this.buildURL()}/${encodePath(backend)}/issuer/${encodePath(issuerRef)}/sign-intermediate`; + const data = serializer.serialize(snapshot, type); + return this.ajax(url, 'POST', { data }).then((result) => ({ + // sign-intermediate can happen multiple times per issuer, + // so the ID needs to be unique from the issuer ID + id: result.request_id, + ...result, + })); + } +} diff --git a/ui/app/models/pki/certificate/base.js b/ui/app/models/pki/certificate/base.js index 541b9fdd2..4bd80f748 100644 --- a/ui/app/models/pki/certificate/base.js +++ b/ui/app/models/pki/certificate/base.js @@ -37,11 +37,11 @@ export default class PkiCertificateBaseModel extends Model { @attr('string') commonName; // Attrs that come back from API POST request - @attr() caChain; + @attr({ masked: true, label: 'CA Chain' }) caChain; @attr('string', { masked: true }) certificate; @attr('number') expiration; @attr('number', { formatDate: true }) revocationTime; - @attr('string') issuingCa; + @attr('string', { label: 'Issuing CA', masked: true }) issuingCa; @attr('string') privateKey; @attr('string') privateKeyType; @attr('string') serialNumber; diff --git a/ui/app/models/pki/issuer.js b/ui/app/models/pki/issuer.js index d7902ea76..17ea33233 100644 --- a/ui/app/models/pki/issuer.js +++ b/ui/app/models/pki/issuer.js @@ -32,8 +32,6 @@ export default class PkiIssuerModel extends PkiCertificateBaseModel { @attr isDefault; // readonly @attr('string') issuerId; - @attr('string', { displayType: 'masked' }) certificate; - @attr('string', { displayType: 'masked', label: 'CA Chain' }) caChain; @attr('string', { label: 'Default key ID', diff --git a/ui/app/models/pki/sign-intermediate.js b/ui/app/models/pki/sign-intermediate.js new file mode 100644 index 000000000..92d93fc9d --- /dev/null +++ b/ui/app/models/pki/sign-intermediate.js @@ -0,0 +1,97 @@ +import { attr } from '@ember-data/model'; +import { withFormFields } from 'vault/decorators/model-form-fields'; +import { withModelValidations } from 'vault/decorators/model-validations'; +import PkiCertificateBaseModel from './certificate/base'; + +const validations = { + csr: [{ type: 'presence', message: 'CSR is required.' }], +}; +@withModelValidations(validations) +@withFormFields([ + 'csr', + 'useCsrValues', + 'commonName', + 'customTtl', + 'notBeforeDuration', + 'format', + 'permittedDnsDomains', + 'maxPathLength', +]) +export default class PkiSignIntermediateModel extends PkiCertificateBaseModel { + getHelpUrl(backend) { + return `/v1/${backend}/issuer/example/sign-intermediate?help=1`; + } + + @attr issuerRef; + + @attr('string', { + label: 'CSR', + editType: 'textarea', + subText: 'The PEM-encoded CSR to be signed.', + }) + csr; + + @attr('boolean', { + label: 'Use CSR values', + subText: + 'Subject information and key usages specified in the CSR will be used over parameters provided here, and extensions in the CSR will be copied into the issued certificate.', + docLink: '/vault/api-docs/secret/pki#use_csr_values', + }) + useCsrValues; + + @attr({ + label: 'Not valid after', + detailsLabel: 'Issued certificates expire after', + subText: + 'The time after which this certificate will no longer be valid. This can be a TTL (a range of time from now) or a specific date.', + editType: 'yield', + }) + customTtl; + + @attr({ + label: 'Backdate validity', + detailsLabel: 'Issued certificate backdating', + helperTextDisabled: 'Vault will use the default value, 30s', + helperTextEnabled: + 'Also called the not_before_duration property. Allows certificates to be valid for a certain time period before now. This is useful to correct clock misalignment on various systems when setting up your CA.', + editType: 'ttl', + defaultValue: '30s', + }) + notBeforeDuration; + + @attr('string') + commonName; + + @attr({ + label: 'Permitted DNS domains', + subText: + 'DNS domains for which certificates are allowed to be issued or signed by this CA certificate. Enter each value as a new input.', + }) + permittedDnsDomains; + + @attr({ + subText: 'Specifies the maximum path length to encode in the generated certificate. -1 means no limit', + defaultValue: '-1', + }) + maxPathLength; + + /* Signing Options overrides */ + @attr({ + label: 'Use PSS', + subText: + 'If checked, PSS signatures will be used over PKCS#1v1.5 signatures when a RSA-type issuer is used. Ignored for ECDSA/Ed25519 issuers.', + }) + usePss; + + @attr({ + label: 'Subject Key Identifier (SKID)', + subText: + 'Value for the subject key identifier, specified as a string in hex format. If this is empty, Vault will automatically calculate the SKID. ', + }) + skid; + + @attr({ + possibleValues: ['0', '256', '384', '512'], + }) + signatureBits; +} diff --git a/ui/app/serializers/pki/role.js b/ui/app/serializers/pki/role.js index 3dde795a8..fbe0d1335 100644 --- a/ui/app/serializers/pki/role.js +++ b/ui/app/serializers/pki/role.js @@ -1,3 +1,7 @@ import ApplicationSerializer from '../application'; -export default class PkiRoleSerializer extends ApplicationSerializer {} +export default class PkiRoleSerializer extends ApplicationSerializer { + attrs = { + name: { serialize: false }, + }; +} diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index 50695e8f4..46428cbbc 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -237,8 +237,11 @@ id={{@attr.name}} value={{or (get @model this.valuePath) @attr.options.defaultValue}} oninput={{this.onChangeWithEvent}} - class="textarea" + class="textarea {{if this.validationError 'has-error-border'}}" > + {{#if this.validationError}} + + {{/if}} {{else if (eq @attr.options.editType "password")}} - {{#if @canRotate}} + {{!-- {{#if @canRotate}} Rotate this root - {{/if}} + {{/if}} --}} {{#if @canCrossSign}} {{/if}} - - Download - - + + + Download + + + + + + + {{#if @canConfigure}} Configure @@ -58,7 +94,7 @@ {{/if}} {{#each fields as |attr|}} - {{#if (eq attr.options.displayType "masked")}} + {{#if attr.options.masked}} > | null; } export default class PkiGenerateToggleGroupsComponent extends Component { @@ -21,6 +22,7 @@ export default class PkiGenerateToggleGroupsComponent extends Component { } get groups() { + if (this.args.groups) return this.args.groups; const groups = { 'Key parameters': this.keyParamFields, 'Subject Alternative Name (SAN) Options': ['altNames', 'ipSans', 'uriSans', 'otherSans'], diff --git a/ui/lib/pki/addon/components/pki-sign-intermediate-form.hbs b/ui/lib/pki/addon/components/pki-sign-intermediate-form.hbs new file mode 100644 index 000000000..2e4c5301b --- /dev/null +++ b/ui/lib/pki/addon/components/pki-sign-intermediate-form.hbs @@ -0,0 +1,81 @@ +{{#if @model.id}} + {{! Model only has ID once form has been submitted and saved }} + +
+
+ + + {{#each this.showFields as |fieldName|}} + {{#let (find-by "name" fieldName @model.allFields) as |attr|}} + + {{#if (and attr.options.masked (get @model attr.name))}} + + {{else if (eq attr.name "serialNumber")}} + {{@model.serialNumber}} + {{else}} + + {{/if}} + + {{/let}} + {{/each}} +
+
+{{else}} +
+
+ + + {{#each @model.formFields as |attr|}} + + {{! attr customTtl has editType yield and will show this component }} + + + {{/each}} + + +
+
+ + + {{#if this.inlineFormAlert}} +
+ +
+ {{/if}} +
+
+{{/if}} \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-sign-intermediate-form.ts b/ui/lib/pki/addon/components/pki-sign-intermediate-form.ts new file mode 100644 index 000000000..80d33cb5e --- /dev/null +++ b/ui/lib/pki/addon/components/pki-sign-intermediate-form.ts @@ -0,0 +1,62 @@ +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import { waitFor } from '@ember/test-waiters'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import PkiIssuerModel from 'vault/models/pki/issuer'; +import FlashMessageService from 'vault/services/flash-messages'; +import errorMessage from 'vault/utils/error-message'; + +interface Args { + onCancel: CallableFunction; + model: PkiIssuerModel; +} + +export default class PkiSignIntermediateFormComponent extends Component { + @service declare readonly flashMessages: FlashMessageService; + @tracked errorBanner = ''; + @tracked inlineFormAlert = ''; + @tracked modelValidations = null; + + @action cancel() { + this.args.model.unloadRecord(); + this.args.onCancel(); + } + @task + @waitFor + *save(event: Event) { + event.preventDefault(); + const { isValid, state, invalidFormMessage } = this.args.model.validate(); + this.modelValidations = isValid ? null : state; + this.inlineFormAlert = invalidFormMessage; + if (!isValid) return; + try { + yield this.args.model.save(); + this.flashMessages.success('Successfully signed CSR.'); + } catch (e) { + this.errorBanner = errorMessage(e); + this.inlineFormAlert = 'There was a problem signing the CSR.'; + } + } + + get groups() { + return { + 'Signing options': ['usePss', 'skid', 'signatureBits'], + 'Subject Alternative Name (SAN) Options': ['altNames', 'ipSans', 'uriSans', 'otherSans'], + 'Additional subject fields': [ + 'ou', + 'organization', + 'country', + 'locality', + 'province', + 'streetAddress', + 'postalCode', + ], + }; + } + + get showFields() { + return ['serialNumber', 'certificate', 'issuingCa', 'caChain']; + } +} diff --git a/ui/lib/pki/addon/routes/issuers/issuer/details.js b/ui/lib/pki/addon/routes/issuers/issuer/details.js index ef04cad3f..23e3e2a3d 100644 --- a/ui/lib/pki/addon/routes/issuers/issuer/details.js +++ b/ui/lib/pki/addon/routes/issuers/issuer/details.js @@ -2,8 +2,30 @@ import PkiIssuerIndexRoute from './index'; export default class PkiIssuerDetailsRoute extends PkiIssuerIndexRoute { // Details route gets issuer data from PkiIssuerIndexRoute - setupController(controller, resolvedModel) { + async setupController(controller, resolvedModel) { super.setupController(controller, resolvedModel); controller.breadcrumbs.push({ label: resolvedModel.id }); + const pem = await this.fetchCertByFormat(resolvedModel.id, 'pem'); + const der = await this.fetchCertByFormat(resolvedModel.id, 'der'); + controller.pem = pem; + controller.der = der; + } + + /** + * @private fetches cert by format so it's available for download + */ + fetchCertByFormat(issuerId, format) { + const endpoint = `/v1/${this.secretMountPath.currentPath}/issuer/${issuerId}/${format}`; + const adapter = this.store.adapterFor('application'); + try { + return adapter.rawRequest(endpoint, 'GET', { unauthenticated: true }).then(function (response) { + if (format === 'der') { + return response.blob(); + } + return response.text(); + }); + } catch (e) { + return null; + } } } diff --git a/ui/lib/pki/addon/routes/issuers/issuer/sign.js b/ui/lib/pki/addon/routes/issuers/issuer/sign.js index dc2476b60..d156c7c54 100644 --- a/ui/lib/pki/addon/routes/issuers/issuer/sign.js +++ b/ui/lib/pki/addon/routes/issuers/issuer/sign.js @@ -1,3 +1,29 @@ import Route from '@ember/routing/route'; +import { service } from '@ember/service'; -export default class PkiIssuerSignRoute extends Route {} +export default class PkiIssuerSignRoute extends Route { + @service store; + @service secretMountPath; + @service pathHelp; + + beforeModel() { + // Must call this promise before the model hook otherwise it doesn't add OpenApi to record. + return this.pathHelp.getNewModel('pki/sign-intermediate', this.secretMountPath.currentPath); + } + + model() { + const { issuer_ref } = this.paramsFor('issuers/issuer'); + return this.store.createRecord('pki/sign-intermediate', { issuerRef: issuer_ref }); + } + setupController(controller, resolvedModel) { + super.setupController(controller, resolvedModel); + const backend = this.secretMountPath.currentPath || 'pki'; + controller.breadcrumbs = [ + { label: 'secrets', route: 'secrets', linkExternal: true }, + { label: backend, route: 'overview' }, + { label: 'issuers', route: 'issuers.index' }, + { label: resolvedModel.issuerRef, route: 'issuers.issuer.details' }, + { label: 'sign intermediate' }, + ]; + } +} diff --git a/ui/lib/pki/addon/templates/issuers/issuer/details.hbs b/ui/lib/pki/addon/templates/issuers/issuer/details.hbs index faaa28c6d..4413ca8b0 100644 --- a/ui/lib/pki/addon/templates/issuers/issuer/details.hbs +++ b/ui/lib/pki/addon/templates/issuers/issuer/details.hbs @@ -9,8 +9,11 @@ + + + + + +

+ + Sign intermediate +

+
+ + + \ No newline at end of file diff --git a/ui/tests/integration/components/pki-sign-intermediate-form-test.js b/ui/tests/integration/components/pki-sign-intermediate-form-test.js new file mode 100644 index 000000000..f344af11d --- /dev/null +++ b/ui/tests/integration/components/pki-sign-intermediate-form-test.js @@ -0,0 +1,97 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'vault/tests/helpers'; +import { click, fillIn, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import Sinon from 'sinon'; +import { setupEngine } from 'ember-engines/test-support'; +import { setupMirage } from 'ember-cli-mirage/test-support'; + +const selectors = { + form: '[data-test-sign-intermediate-form]', + csrInput: '[data-test-input="csr"]', + toggleSigningOptions: '[data-test-toggle-group="Signing options"]', + toggleSANOptions: '[data-test-toggle-group="Subject Alternative Name (SAN) Options"]', + toggleAdditionalFields: '[data-test-toggle-group="Additional subject fields"]', + fieldByName: (name) => `[data-test-field="${name}"]`, + saveButton: '[data-test-pki-sign-intermediate-save]', + cancelButton: '[data-test-pki-sign-intermediate-cancel]', + fieldError: '[data-test-inline-alert]', + formError: '[data-test-form-error]', + resultsContainer: '[data-test-sign-intermediate-result]', + rowByName: (name) => `[data-test-row-label="${name}"]`, + valueByName: (name) => `[data-test-value-div="${name}"]`, +}; +module('Integration | Component | pki-sign-intermediate-form', function (hooks) { + setupRenderingTest(hooks); + setupEngine(hooks, 'pki'); + setupMirage(hooks); + + hooks.beforeEach(async function () { + this.store = this.owner.lookup('service:store'); + this.secretMountPath = this.owner.lookup('service:secret-mount-path'); + this.secretMountPath.currentPath = 'pki-test'; + this.model = this.store.createRecord('pki/sign-intermediate', { issuerRef: 'some-issuer' }); + this.onCancel = Sinon.spy(); + }); + + test('renders correctly on load', async function (assert) { + assert.expect(9); + await render(hbs``, { + owner: this.engine, + }); + + assert.dom(selectors.form).exists('Form is rendered'); + assert.dom(selectors.resultsContainer).doesNotExist('Results display not rendered'); + assert.dom('[data-test-field]').exists({ count: 8 }, '8 default fields shown'); + assert.dom(selectors.toggleSigningOptions).exists(); + assert.dom(selectors.toggleSANOptions).exists(); + assert.dom(selectors.toggleAdditionalFields).exists(); + + await click(selectors.toggleSigningOptions); + ['usePss', 'skid', 'signatureBits'].forEach((name) => { + assert.dom(selectors.fieldByName(name)).exists(); + }); + }); + + test('it shows the returned values on successful save', async function (assert) { + assert.expect(13); + await render(hbs``, { + owner: this.engine, + }); + + this.server.post(`/pki-test/issuer/some-issuer/sign-intermediate`, function (schema, req) { + const payload = JSON.parse(req.requestBody); + assert.strictEqual(payload.csr, 'example-data', 'Request made to correct endpoint on save'); + return { + request_id: 'some-id', + data: { + serial_number: '31:52:b9:09:40', + ca_chain: ['-----root pem------'], + issuing_ca: '-----issuing ca------', + certificate: '-----certificate------', + }, + }; + }); + await click(selectors.saveButton); + assert.dom(selectors.formError).hasText('There is an error with this form.', 'Shows validation errors'); + assert.dom(selectors.csrInput).hasClass('has-error-border'); + assert.dom(selectors.fieldError).hasText('CSR is required.'); + + await fillIn(selectors.csrInput, 'example-data'); + await click(selectors.saveButton); + [ + { label: 'Serial number' }, + { label: 'CA Chain', masked: true }, + { label: 'Certificate', masked: true }, + { label: 'Issuing CA', masked: true }, + ].forEach(({ label, masked }) => { + assert.dom(selectors.rowByName(label)).exists(); + if (masked) { + assert.dom(selectors.valueByName(label)).hasText('***********', `${label} is masked`); + } 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`); + } + }); + }); +}); diff --git a/ui/tests/integration/components/pki/pki-generate-toggle-groups-test.js b/ui/tests/integration/components/pki/pki-generate-toggle-groups-test.js index 7e603f6b7..103084d9a 100644 --- a/ui/tests/integration/components/pki/pki-generate-toggle-groups-test.js +++ b/ui/tests/integration/components/pki/pki-generate-toggle-groups-test.js @@ -8,6 +8,7 @@ const selectors = { keys: '[data-test-toggle-group="Key parameters"]', sanOptions: '[data-test-toggle-group="Subject Alternative Name (SAN) Options"]', subjectFields: '[data-test-toggle-group="Additional subject fields"]', + toggleByName: (name) => `[data-test-toggle-group="${name}"]`, }; module('Integration | Component | PkiGenerateToggleGroups', function (hooks) { @@ -94,4 +95,32 @@ module('Integration | Component | PkiGenerateToggleGroups', function (hooks) { assert.dom(`[data-test-input="${key}"]`).exists(`${key} input renders`); }); }); + + test('it should render groups according to the passed @groups', async function (assert) { + assert.expect(11); + const fieldsA = ['ou', 'organization']; + const fieldsZ = ['country', 'locality', 'province', 'streetAddress', 'postalCode']; + this.set('groups', { + 'Group A': fieldsA, + 'Group Z': fieldsZ, + }); + await render(hbs``, { + owner: this.engine, + }); + + assert.dom(selectors.toggleByName('Group A')).hasText('Group A', 'First group renders'); + assert.dom(selectors.toggleByName('Group Z')).hasText('Group Z', 'Second group renders'); + + await click(selectors.toggleByName('Group A')); + assert.dom('[data-test-field]').exists({ count: fieldsA.length }, 'Correct number of fields render'); + fieldsA.forEach((key) => { + assert.dom(`[data-test-input="${key}"]`).exists(`${key} input renders`); + }); + + await click(selectors.toggleByName('Group Z')); + assert.dom('[data-test-field]').exists({ count: fieldsZ.length }, 'Correct number of fields render'); + fieldsZ.forEach((key) => { + assert.dom(`[data-test-input="${key}"]`).exists(`${key} input renders`); + }); + }); }); diff --git a/ui/tests/integration/components/pki/pki-issuer-details-test.js b/ui/tests/integration/components/pki/pki-issuer-details-test.js index 8391e0397..c7725cbf4 100644 --- a/ui/tests/integration/components/pki/pki-issuer-details-test.js +++ b/ui/tests/integration/components/pki/pki-issuer-details-test.js @@ -38,7 +38,8 @@ module('Integration | Component | page/pki-issuer-details', function (hooks) { this.context ); - assert.dom(SELECTORS.rotateRoot).hasText('Rotate this root'); + // Add back when rotate root capability is added + // assert.dom(SELECTORS.rotateRoot).hasText('Rotate this root'); assert.dom(SELECTORS.crossSign).hasText('Cross-sign Issuer'); assert.dom(SELECTORS.signIntermediate).hasText('Sign Intermediate'); assert.dom(SELECTORS.download).hasText('Download'); diff --git a/ui/tests/integration/components/pki/pki-role-form-test.js b/ui/tests/integration/components/pki/pki-role-form-test.js index 5a1fcc73d..9aacc6936 100644 --- a/ui/tests/integration/components/pki/pki-role-form-test.js +++ b/ui/tests/integration/components/pki/pki-role-form-test.js @@ -46,17 +46,15 @@ module('Integration | Component | pki-role-form', function (hooks) { test('it should save a new pki role with various options selected', async function (assert) { // Key usage, Key params and Not valid after options are tested in their respective component tests - assert.expect(10); + assert.expect(9); this.server.post(`/${this.model.backend}/roles/test-role`, (schema, req) => { assert.ok(true, 'Request made to save role'); const request = JSON.parse(req.requestBody); - const roleName = request.name; const allowedDomainsTemplate = request.allowed_domains_template; const policyIdentifiers = request.policy_identifiers; const allowedUriSansTemplate = request.allow_uri_sans_template; const allowedSerialNumbers = request.allowed_serial_numbers; - assert.strictEqual(roleName, 'test-role', 'correctly sends the role name'); assert.true(allowedDomainsTemplate, 'correctly sends allowed_domains_template'); assert.strictEqual(policyIdentifiers[0], 'some-oid', 'correctly sends policy_identifiers'); assert.true(allowedUriSansTemplate, 'correctly sends allowed_uri_sans_template'); diff --git a/ui/tests/unit/adapters/pki/sign-intermediate-test.js b/ui/tests/unit/adapters/pki/sign-intermediate-test.js new file mode 100644 index 000000000..9e506853f --- /dev/null +++ b/ui/tests/unit/adapters/pki/sign-intermediate-test.js @@ -0,0 +1,40 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'vault/tests/helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; + +module('Unit | Adapter | pki/sign-intermediate', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + this.secretMountPath = this.owner.lookup('service:secret-mount-path'); + this.backend = 'pki-test'; + this.secretMountPath.currentPath = this.backend; + this.payload = { + issuerRef: 'my-issuer-id', + }; + }); + + test('it exists', function (assert) { + const adapter = this.owner.lookup('adapter:pki/sign-intermediate'); + assert.ok(adapter); + }); + + test('it calls the correct endpoint on save', async function (assert) { + assert.expect(2); + + this.server.post(`${this.backend}/issuer/my-issuer-id/sign-intermediate`, () => { + assert.ok(true, 'correct endpoint called'); + return { + request_id: 'unique-request-id', + data: { + foo: 'bar', + }, + }; + }); + + const result = await this.store.createRecord('pki/sign-intermediate', this.payload).save(); + assert.strictEqual(result.id, 'unique-request-id', 'Resulting model has ID matching request ID'); + }); +}); diff --git a/ui/types/vault/models/pki/issuer.d.ts b/ui/types/vault/models/pki/issuer.d.ts index b5a59e7de..7316d9789 100644 --- a/ui/types/vault/models/pki/issuer.d.ts +++ b/ui/types/vault/models/pki/issuer.d.ts @@ -1,6 +1,5 @@ import PkiCertificateBaseModel from './certificate/base'; -import { FormField, FormFieldGroups } from 'vault/app-types'; - +import { FormField, FormFieldGroups, ModelValidations } from 'vault/app-types'; export default class PkiIssuerModel extends PkiCertificateBaseModel { useOpenAPI(): boolean; issuerId: string; @@ -18,12 +17,14 @@ export default class PkiIssuerModel extends PkiCertificateBaseModel { rotateInternal: any; rotateExisting: any; crossSignPath: any; - signIntermediate: any; + signIntermediate: any; -------------------- **/ formFields: Array; formFieldGroups: FormFieldGroups; + allFields: Array; get canRotateIssuer(): boolean; get canCrossSign(): boolean; get canSignIntermediate(): boolean; get canConfigure(): boolean; + validate(): ModelValidations; }