From a76bbcfe8446006f38b2c546f0e33125bae21d58 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:46:25 -0700 Subject: [PATCH] ui: pki import key (#18454) * Move text-file to addon * create key import component * build out import component * add perform helper * small text-file changes * add file to import component * revert text-filechanges * Revert "small text-file changes" This reverts commit dc4c4864a3165b48daa9d3dfc0c03d6bf073fd46. * small text-file changes * remove index from policy set file onchange arg * Revert "remove index from policy set file onchange arg" This reverts commit e80198e063f4886d242359da25bfb2a63a811171. * Revert "small text-file changes" This reverts commit bc3ebccc4cc658431729ea4d6ffff2c17d2fd4ba. * finish key import * update key adapter * address comments * remove validations from import and unnecessary store service * add waitfor to key form * fix prettier * import changes from edit pki key pr * add waitFor to concurrency task * add adapter options to form save method Co-authored-by: Chelsea Shaw --- ui/app/adapters/pki/key.js | 33 +++++----- ui/app/models/pki/key.js | 9 ++- ui/lib/pki/addon/components/pki-key-form.js | 5 +- .../pki/addon/components/pki-key-import.hbs | 45 ++++++++++++++ ui/lib/pki/addon/components/pki-key-import.js | 60 +++++++++++++++++++ ui/lib/pki/addon/routes/keys/import.js | 18 +++++- ui/lib/pki/addon/templates/keys/import.hbs | 17 +++++- 7 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 ui/lib/pki/addon/components/pki-key-import.hbs create mode 100644 ui/lib/pki/addon/components/pki-key-import.js diff --git a/ui/app/adapters/pki/key.js b/ui/app/adapters/pki/key.js index 4a0c35357..a999a28cd 100644 --- a/ui/app/adapters/pki/key.js +++ b/ui/app/adapters/pki/key.js @@ -3,9 +3,22 @@ import { encodePath } from 'vault/utils/path-encoding-helpers'; export default class PkiKeyAdapter extends ApplicationAdapter { namespace = 'v1'; + _baseUrl(backend, id) { + const url = `${this.buildURL()}/${encodePath(backend)}`; + if (id) { + return url + '/key/' + encodePath(id); + } + return url + '/keys'; + } + createRecord(store, type, snapshot) { - const { record } = snapshot; - const url = this.getUrl(record.backend) + '/generate/' + record.type; + const { record, adapterOptions } = snapshot; + let url = this._baseUrl(record.backend); + if (adapterOptions.import) { + url = `${url}/import`; + } else { + url = `${url}/generate/${record.type}`; + } return this.ajax(url, 'POST', { data: this.serialize(snapshot) }).then((resp) => { return resp; }); @@ -14,30 +27,22 @@ export default class PkiKeyAdapter extends ApplicationAdapter { updateRecord(store, type, snapshot) { const { record } = snapshot; const { key_name } = this.serialize(snapshot); - const url = this.getUrl(record.backend, record.id); + const url = this._baseUrl(record.backend, record.id); return this.ajax(url, 'POST', { data: { key_name } }); } - getUrl(backend, id) { - const url = `${this.buildURL()}/${encodePath(backend)}`; - if (id) { - return url + '/key/' + encodePath(id); - } - return url + '/keys'; - } - query(store, type, query) { const { backend } = query; - return this.ajax(this.getUrl(backend), 'GET', { data: { list: true } }); + return this.ajax(this._baseUrl(backend), 'GET', { data: { list: true } }); } queryRecord(store, type, query) { const { backend, id } = query; - return this.ajax(this.getUrl(backend, id), 'GET'); + return this.ajax(this._baseUrl(backend, id), 'GET'); } deleteRecord(store, type, snapshot) { const { id, record } = snapshot; - return this.ajax(this.getUrl(record.backend, id), 'DELETE'); + return this.ajax(this._baseUrl(record.backend, id), 'DELETE'); } } diff --git a/ui/app/models/pki/key.js b/ui/app/models/pki/key.js index b428200bc..f2f44238a 100644 --- a/ui/app/models/pki/key.js +++ b/ui/app/models/pki/key.js @@ -16,8 +16,10 @@ export default class PkiKeyModel extends Model { @service secretMountPath; @attr('string', { detailsLabel: 'Key ID' }) keyId; - @attr('string', { subText: 'Optional, human-readable name for this key.' }) keyName; - @attr('string') privateKey; + @attr('string', { + subText: `Optional, human-readable name for this key. The name must be unique across all keys and cannot be 'default'.`, + }) + keyName; @attr('string', { noDefault: true, possibleValues: ['internal', 'exported'], @@ -38,6 +40,9 @@ export default class PkiKeyModel extends Model { }) keyBits; // no possibleValues because dependent on selected key type + @attr('string') pemBundle; + @attr('string') privateKey; + get backend() { return this.secretMountPath.currentPath; } diff --git a/ui/lib/pki/addon/components/pki-key-form.js b/ui/lib/pki/addon/components/pki-key-form.js index 0676d44ff..cbb3dd0f7 100644 --- a/ui/lib/pki/addon/components/pki-key-form.js +++ b/ui/lib/pki/addon/components/pki-key-form.js @@ -4,6 +4,7 @@ import { inject as service } from '@ember/service'; import { task } from 'ember-concurrency'; import { tracked } from '@glimmer/tracking'; import errorMessage from 'vault/utils/error-message'; +import { waitFor } from '@ember/test-waiters'; /** * @module PkiKeyForm @@ -20,7 +21,6 @@ import errorMessage from 'vault/utils/error-message'; */ export default class PkiKeyForm extends Component { - @service store; @service flashMessages; @tracked errorBanner; @@ -28,6 +28,7 @@ export default class PkiKeyForm extends Component { @tracked modelValidations; @task + @waitFor *save(event) { event.preventDefault(); try { @@ -38,7 +39,7 @@ export default class PkiKeyForm extends Component { this.invalidFormAlert = invalidFormMessage; } if (!isValid && isNew) return; - yield this.args.model.save(); + yield this.args.model.save({ adapterOptions: { import: false } }); this.flashMessages.success(`Successfully ${isNew ? 'generated' : 'updated'} the key ${keyName}.`); this.args.onSave(); } catch (error) { diff --git a/ui/lib/pki/addon/components/pki-key-import.hbs b/ui/lib/pki/addon/components/pki-key-import.hbs new file mode 100644 index 000000000..1f541fc26 --- /dev/null +++ b/ui/lib/pki/addon/components/pki-key-import.hbs @@ -0,0 +1,45 @@ +
+ +
+

+ Use this form to import a single pem encoded rsa, ec, or ed25519 key. + + Learn more here. + +

+ {{#let (find-by "name" "keyName" @model.formFields) as |attr|}} + + {{/let}} + +
+
+ + + {{#if this.invalidFormAlert}} +
+ +
+ {{/if}} +
+ \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-key-import.js b/ui/lib/pki/addon/components/pki-key-import.js new file mode 100644 index 000000000..a5fea433e --- /dev/null +++ b/ui/lib/pki/addon/components/pki-key-import.js @@ -0,0 +1,60 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import errorMessage from 'vault/utils/error-message'; +import trimRight from 'vault/utils/trim-right'; +import { waitFor } from '@ember/test-waiters'; + +// TODO: convert to typescript after https://github.com/hashicorp/vault/pull/18387 is merged +/** + * @module PkiKeyImport + * PkiKeyImport components are used to import PKI keys. + * + * @example + * ```js + * + * ``` + * + * @param {Object} model - pki/key model. + * @callback onCancel - Callback triggered when cancel button is clicked. + * @callback onSubmit - Callback triggered on submit success. + */ + +export default class PkiKeyImport extends Component { + @service flashMessages; + + @tracked errorBanner; + @tracked invalidFormAlert; + + @task + @waitFor + *submitForm(event) { + event.preventDefault(); + try { + const { keyName } = this.args.model; + yield this.args.model.save({ adapterOptions: { import: true } }); + this.flashMessages.success(`Successfully imported key ${keyName}`); + this.args.onSave(); + } catch (error) { + this.errorBanner = errorMessage(error); + this.invalidFormAlert = 'There was a problem importing key.'; + } + } + + @action + onFileUploaded({ value, filename }) { + this.args.model.pemBundle = value; + if (!this.args.model.keyName) { + const trimmedFileName = trimRight(filename, ['.json', '.pem']); + this.args.model.keyName = trimmedFileName; + } + } + + @action + cancel() { + this.args.model.unloadRecord(); + this.args.onCancel(); + } +} diff --git a/ui/lib/pki/addon/routes/keys/import.js b/ui/lib/pki/addon/routes/keys/import.js index caaaf9100..dac4e2e4f 100644 --- a/ui/lib/pki/addon/routes/keys/import.js +++ b/ui/lib/pki/addon/routes/keys/import.js @@ -1,3 +1,17 @@ -import Route from '@ember/routing/route'; +import PkiKeysIndexRoute from '.'; +import { inject as service } from '@ember/service'; +import { withConfirmLeave } from 'core/decorators/confirm-leave'; -export default class PkiKeysImportRoute extends Route {} +@withConfirmLeave() +export default class PkiKeysImportRoute extends PkiKeysIndexRoute { + @service store; + + model() { + return this.store.createRecord('pki/key'); + } + + setupController(controller, resolvedModel) { + super.setupController(controller, resolvedModel); + controller.breadcrumbs.push({ label: 'import' }); + } +} diff --git a/ui/lib/pki/addon/templates/keys/import.hbs b/ui/lib/pki/addon/templates/keys/import.hbs index 8c9d243c1..8b03df5f1 100644 --- a/ui/lib/pki/addon/templates/keys/import.hbs +++ b/ui/lib/pki/addon/templates/keys/import.hbs @@ -1 +1,16 @@ -keys.import \ No newline at end of file + + + + + +

+ Import key +

+
+
+ + \ No newline at end of file