open-vault/ui/app/adapters/keymgmt/key.js
Chelsea Shaw 81105e6209
UI: keymgmt secret engine (#15523)
* No default provider on create, add subText to service_account_file field

* Show empty state if no provider selected -- sorry for all the conditionals

* Button and distribution title styling on key edit

* Fix key distribute empty state permissions

* Don't try to fetch distribution if provider is permissionError

* Use search-select component for provider on distribute component

* Show distribution form errors on page rather than popup

* Add id, label, subtext to input-search for search-select fallback

* Remove created field from provider, default to querying for keys unless capabilities is false

* Fix link to provider from key-edit

* Search select label styling and add subText to fallback

* Refetch model after key rotate

* Create distribution method is task so we can load and disable button

* Move keymgmt to cloud group on mount options

* Key actions are tasks, fix tab active class

* Add isRunning attr to confirm-action which disables confirm button and replaces text with loader

* Fix provider active tab class

* Handle control groups on distribution

* Correctly handle error message on key-edit

* Show loading state on distribute, reload key after distribute

* Clear old validation errors if valid

* Fix tests

* Fix delete url

* Add changelog

* Address PR comments

* kick circle-ci

* Format go file breaking fmt

* Rename old changelog

* Remove resolved TODO
2022-05-20 10:41:24 -05:00

172 lines
4.8 KiB
JavaScript

import ApplicationAdapter from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';
import ControlGroupError from '../../lib/control-group-error';
function pickKeys(obj, picklist) {
const data = {};
Object.keys(obj).forEach((key) => {
if (picklist.indexOf(key) >= 0) {
data[key] = obj[key];
}
});
return data;
}
export default class KeymgmtKeyAdapter extends ApplicationAdapter {
namespace = 'v1';
pathForType() {
// backend name prepended in buildURL method
return 'key';
}
buildURL(modelName, id, snapshot, requestType, query) {
let url = super.buildURL(...arguments);
if (snapshot) {
url = url.replace('key', `${snapshot.attr('backend')}/key`);
} else if (query) {
url = url.replace('key', `${query.backend}/key`);
}
return url;
}
url(backend, id, type) {
const url = `${this.buildURL()}/${backend}/key`;
if (id) {
if (type === 'ROTATE') {
return url + '/' + encodePath(id) + '/rotate';
} else if (type === 'PROVIDERS') {
return url + '/' + encodePath(id) + '/kms';
}
return url + '/' + encodePath(id);
}
return url;
}
_updateKey(backend, name, serialized) {
// Only these two attributes are allowed to be updated
let data = pickKeys(serialized, ['deletion_allowed', 'min_enabled_version']);
return this.ajax(this.url(backend, name), 'PUT', { data });
}
_createKey(backend, name, serialized) {
// Only type is allowed on create
let data = pickKeys(serialized, ['type']);
return this.ajax(this.url(backend, name), 'POST', { data });
}
async createRecord(store, type, snapshot) {
const data = store.serializerFor(type.modelName).serialize(snapshot);
const name = snapshot.attr('name');
const backend = snapshot.attr('backend');
// Keys must be created and then updated
await this._createKey(backend, name, data);
if (snapshot.attr('deletionAllowed')) {
try {
await this._updateKey(backend, name, data);
} catch {
throw new Error(`Key ${name} was created, but not all settings were saved`);
}
}
return {
data: {
...data,
id: name,
backend,
},
};
}
updateRecord(store, type, snapshot) {
const data = store.serializerFor(type.modelName).serialize(snapshot);
const name = snapshot.attr('name');
const backend = snapshot.attr('backend');
return this._updateKey(backend, name, data);
}
distribute(backend, kms, key, data) {
return this.ajax(`${this.buildURL()}/${backend}/kms/${encodePath(kms)}/key/${encodePath(key)}`, 'PUT', {
data: { ...data },
});
}
async getProvider(backend, name) {
try {
const resp = await this.ajax(this.url(backend, name, 'PROVIDERS'), 'GET', {
data: {
list: true,
},
});
return resp.data.keys ? resp.data.keys[0] : null;
} catch (e) {
if (e.httpStatus === 404) {
// No results, not distributed yet
return null;
} else if (e.httpStatus === 403) {
return { permissionsError: true };
}
throw e;
}
}
getDistribution(backend, kms, key) {
const url = `${this.buildURL()}/${backend}/kms/${kms}/key/${key}`;
return this.ajax(url, 'GET')
.then((res) => {
return {
...res.data,
purposeArray: res.data.purpose.split(','),
};
})
.catch((e) => {
if (e instanceof ControlGroupError) {
throw e;
}
return null;
});
}
async queryRecord(store, type, query) {
const { id, backend, recordOnly = false } = query;
const keyData = await this.ajax(this.url(backend, id), 'GET');
keyData.data.id = id;
keyData.data.backend = backend;
let provider, distribution;
if (!recordOnly) {
provider = await this.getProvider(backend, id);
if (provider && !provider.permissionsError) {
distribution = await this.getDistribution(backend, provider, id);
}
}
return { ...keyData, provider, distribution };
}
async query(store, type, query) {
const { backend, provider } = query;
const providerAdapter = store.adapterFor('keymgmt/provider');
const url = provider ? providerAdapter.buildKeysURL(query) : this.url(backend);
return this.ajax(url, 'GET', {
data: {
list: true,
},
}).then((res) => {
res.backend = backend;
return res;
});
}
async rotateKey(backend, id) {
let keyModel = this.store.peekRecord('keymgmt/key', id);
const result = await this.ajax(this.url(backend, id, 'ROTATE'), 'PUT');
await keyModel.reload();
return result;
}
removeFromProvider(model) {
const url = `${this.buildURL()}/${model.backend}/kms/${model.provider}/key/${model.name}`;
return this.ajax(url, 'DELETE').then(() => {
model.provider = null;
});
}
}