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 <cshaw@hashicorp.com>
This commit is contained in:
parent
424e7439dc
commit
a76bbcfe84
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<form {{on "submit" (perform this.submitForm)}} data-test-pki-key-import-form>
|
||||
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<p class="has-bottom-margin-l">
|
||||
Use this form to import a single pem encoded rsa, ec, or ed25519 key.
|
||||
<DocLink @path="/vault/api-docs/secret/pki#import-key">
|
||||
Learn more here.
|
||||
</DocLink>
|
||||
</p>
|
||||
{{#let (find-by "name" "keyName" @model.formFields) as |attr|}}
|
||||
<FormField data-test-field={{attr}} @attr={{attr}} @model={{@model}} @showHelpText={{false}} />
|
||||
{{/let}}
|
||||
<TextFile @onChange={{this.onFileUploaded}} @label="PEM Bundle" data-test-pki-key-file />
|
||||
</div>
|
||||
<div class="has-top-padding-s">
|
||||
<button
|
||||
type="submit"
|
||||
class="button is-primary {{if this.submitForm.isRunning 'is-loading'}}"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
data-test-pki-key-import
|
||||
>
|
||||
Import key
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button has-left-margin-s"
|
||||
disabled={{this.submitForm.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
data-test-pki-key-cancel
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{{#if this.invalidFormAlert}}
|
||||
<div class="control">
|
||||
<AlertInline
|
||||
@type="danger"
|
||||
@paddingTop={{true}}
|
||||
@message={{this.invalidFormAlert}}
|
||||
@mimicRefresh={{true}}
|
||||
data-test-pki-key-validation-error
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
|
@ -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
|
||||
* <PkiKeyImport @model={{this.model}} />
|
||||
* ```
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,16 @@
|
|||
keys.import
|
||||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3" data-test-pki-key-page-title>
|
||||
Import key
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<PkiKeyImport
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.keys.index"}}
|
||||
@onSave={{transition-to "vault.cluster.secrets.backend.pki.keys.key.details" this.model.id}}
|
||||
/>
|
Loading…
Reference in New Issue