open-vault/ui/app/components/keymgmt/distribute.js
Chelsea Shaw 698a652a92
UI: Remove Wizard (#19220)
* Remove UI Wizard temporarily [GH-19000]
2023-02-16 22:44:33 +00:00

266 lines
7.6 KiB
JavaScript

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { KEY_TYPES } from '../../models/keymgmt/key';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
/**
* @module KeymgmtDistribute
* KeymgmtDistribute components are used to provide a form to distribute Keymgmt keys to a provider.
*
* @example
* ```js
* <KeymgmtDistribute @backend="keymgmt" @key="my-key" @provider="my-kms" />
* ```
* @param {string} backend - name of backend, which will be the basis of other store queries
* @param {string} [key] - key is the name of the existing key which is being distributed. Will hide the key field in UI
* @param {string} [provider] - provider is the name of the existing provider which is being distributed to. Will hide the provider field in UI
*/
class DistributionData {
@tracked key;
@tracked provider;
@tracked operations;
@tracked protection;
}
const VALID_TYPES_BY_PROVIDER = {
gcpckms: ['aes256-gcm96', 'rsa-2048', 'rsa-3072', 'rsa-4096', 'ecdsa-p256', 'ecdsa-p384', 'ecdsa-p521'],
awskms: ['aes256-gcm96'],
azurekeyvault: ['rsa-2048', 'rsa-3072', 'rsa-4096'],
};
export default class KeymgmtDistribute extends Component {
@service store;
@service flashMessages;
@service router;
@tracked keyModel;
@tracked isNewKey = false;
@tracked providerType;
@tracked formData;
@tracked formErrors;
constructor() {
super(...arguments);
this.formData = new DistributionData();
// Set initial values passed in
this.formData.key = this.args.key || '';
this.formData.provider = this.args.provider || '';
// Side effects to get types of key or provider passed in
if (this.args.provider) {
this.getProviderType(this.args.provider);
}
if (this.args.key) {
this.getKeyInfo(this.args.key);
}
this.formData.operations = [];
}
get keyTypes() {
return KEY_TYPES;
}
get validMatchError() {
if (!this.providerType || !this.keyModel?.type) {
return null;
}
const valid = VALID_TYPES_BY_PROVIDER[this.providerType]?.includes(this.keyModel.type);
if (valid) return null;
// default to showing error on provider unless @provider (field hidden)
if (this.args.provider) {
return {
key: `This key type is incompatible with the ${this.providerType} provider. To distribute to this provider, change the key type or choose another key.`,
};
}
const message = `This provider is incompatible with the ${this.keyModel.type} key type. Please choose another provider`;
return {
provider: this.args.key ? `${message}.` : `${message} or change the key type.`,
};
}
get operations() {
const pt = this.providerType;
if (pt === 'awskms') {
return ['encrypt', 'decrypt'];
} else if (pt === 'gcpckms') {
const kt = this.keyModel?.type || '';
switch (kt) {
case 'aes256-gcm96':
return ['encrypt', 'decrypt'];
case 'rsa-2048':
case 'rsa-3072':
case 'rsa-4096':
return ['decrypt', 'sign'];
case 'ecdsa-p256':
case 'ecdsa-p384':
return ['sign'];
default:
return ['encrypt', 'decrypt', 'sign', 'verify', 'wrap', 'unwrap'];
}
}
return ['encrypt', 'decrypt', 'sign', 'verify', 'wrap', 'unwrap'];
}
get disableOperations() {
return (
this.validMatchError ||
!this.formData.provider ||
!this.formData.key ||
(this.isNewKey && !this.keyModel.type)
);
}
async getKeyInfo(keyName, isNew = false) {
let key;
if (isNew) {
this.isNewKey = true;
key = this.store.createRecord(`keymgmt/key`, {
backend: this.args.backend,
id: keyName,
name: keyName,
});
} else {
key = await this.store
.queryRecord(`keymgmt/key`, {
backend: this.args.backend,
id: keyName,
recordOnly: true,
})
.catch(() => {
// Key type isn't essential for distributing, so if
// we can't read it for some reason swallow the error
// and allow the API to respond with any key/provider
// type matching errors
});
}
this.keyModel = key;
}
async getProviderType(id) {
if (!id) {
this.providerType = '';
return;
}
const provider = await this.store
.queryRecord('keymgmt/provider', {
backend: this.args.backend,
id,
})
.catch(() => {});
this.providerType = provider?.provider;
}
destroyKey() {
if (this.isNewKey) {
// Delete record from store if it was created here
this.keyModel.destroyRecord().finally(() => {
this.keyModel = null;
});
}
this.isNewKey = false;
this.keyModel = null;
}
/**
*
* @param {DistributionData} rawData
* @returns POJO formatted how the distribution endpoint needs
*/
formatData(rawData) {
const { key, provider, operations, protection } = rawData;
if (!key || !provider || !operations || operations.length === 0) return null;
return { key, provider, purpose: operations.join(','), protection };
}
distributeKey(backend, data) {
const adapter = this.store.adapterFor('keymgmt/key');
const { key, provider, purpose, protection } = data;
return adapter
.distribute(backend, provider, key, { purpose, protection })
.then(() => {
this.flashMessages.success(`Successfully distributed key ${key} to ${provider}`);
// update keys on provider model
this.store.clearDataset('keymgmt/key');
const providerModel = this.store.peekRecord('keymgmt/provider', provider);
providerModel.fetchKeys(providerModel.keys?.meta?.currentPage || 1);
this.args.onClose();
})
.catch((e) => {
this.formErrors = `${e.errors}`;
});
}
@action
handleProvider(selection) {
let providerName = selection[0];
if (typeof selection === 'string') {
// Handles case if no list permissions and fallback component is used
providerName = selection;
}
this.formData.provider = providerName;
if (providerName) {
this.getProviderType(providerName);
}
}
@action
handleKeyType(evt) {
this.keyModel.set('type', evt.target.value);
}
@action
handleOperation(evt) {
const ops = [...this.formData.operations];
if (evt.target.checked) {
ops.push(evt.target.id);
} else {
const idx = ops.indexOf(evt.target.id);
ops.splice(idx, 1);
}
this.formData.operations = ops;
}
@action
async handleKeySelect(selected) {
const selectedKey = selected[0] || null;
if (!selectedKey) {
this.formData.key = null;
return this.destroyKey();
}
this.formData.key = selectedKey.id;
return this.getKeyInfo(selectedKey.id, selectedKey.isNew);
}
@task
@waitFor
*createDistribution(evt) {
evt.preventDefault();
const { backend } = this.args;
const data = this.formatData(this.formData);
if (!data) {
this.flashMessages.danger(`Key, provider, and operations are all required`);
return;
}
if (this.isNewKey) {
try {
yield this.keyModel.save();
this.flashMessages.success(`Successfully created key ${this.keyModel.name}`);
} catch (e) {
this.flashMessages.danger(`Error creating new key ${this.keyModel.name}: ${e.errors}`);
return;
}
}
yield this.distributeKey(backend, data);
// Reload key to get dist info
yield this.store.queryRecord(`keymgmt/key`, {
backend: this.args.backend,
id: this.keyModel.name,
});
}
}