Skip to content

Commit

Permalink
UI: Transform secrets engine with transformations
Browse files Browse the repository at this point in the history
* Ui/transform enable (#9647)

* Show Transform on engines list if enterprise

* Add box-radio component

* Add is-disabled styling for box-radio and fix tooltip styling when position: above

* Add KMIP and Transform to possible features on has feature helper

* Sidebranch: Transform Secret Engine Initial setup (#9625)

* WIP // list transforms, console.logs and all

* setup LIST transformations ajax request and draft out options-for-backend options

* change from plural to singluar and add transform to secret-edit

* create two transform edit components

* modify transform model with new attrs

* add adapterFor to connect transform adapter to transform-edit-form component

* setup Allowed roles searchSelect component to search over new transform/role adapter and model.

* clean up for PR

* clean up linting errors

* restructure adapter call, now it works.

* remove console

* setup template model for SearchSelect component

* add props to form field and search select for styling

Co-authored-by: Chelsea Shaw <chelshaw.dev@gmail.com>

* Ui/transform language fixes (#9666)

* Update casing and wording on Transform list route. Use generic list item for transformations

* Add back js file for transformation-edit

* Set up transform for tabs

* Ui/create edit transformation fixes (#9668)

* add conditional for masking vs tweak source based on type, and update text for create transformation

* change order

* fix error with stringArray

* setup the edit/delete transformation view

* clean up toolbar links

* setup serializer to change response of mask character from keycode to character

* change styling of label and sub-text size, confirmed with design

* temp fix on templates vs template

* add clickable list item

* add space between template list

* setup styling and structure for the rest of the show transformation.  TODO: turn into components.

* create transform-show-transformation component

* add attachCapabilities to transform model and update transform-transformation-itme list accordingly

* clean up liniting errors

* address pr comments

* remove leftover

* clean up

* Sidebranch: UI transform create and edit clean up (#9778)

* clean up some of the TODOs

* setup edit view with read only attributes for name and template

* setup initial selected for search select component

* fixes

* hide templates form field for now

* set selectLimit for search select component

* hide power select if the select limit is greater than or equal to the selectedOptions length

* clean up failing linting

* address pr comments

* Ui/fix list roles transformation (#9788)

* Update search-select to pass backend to query if exists

* Update role and template adapters

* cleanup

* Fix replace with static string

* Ui/transform cleanup 2 (#9789)

* amend encode/decode commands for now until design gets back with more details

* restrict character count on masking input field

* clean up selectLimit

* show backend instead of transform in cli copy command

* Show KMIP un-selectable if enterprise but no ADP module (#9780)

* New component transform-edit-base

* Duplicate RoleEdit as TransformEditBase and swap in all transform components

* Roll back role-edit changes

* Update to transform edit base

* Remove extraeneous set backend type on transform components

* formatting

* Revert search-select changes

* Update template/templates data on transformation (#9838)

Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
  • Loading branch information
chelshaw and Monkeychip committed Aug 26, 2020
1 parent ade448c commit 6fb9768
Show file tree
Hide file tree
Showing 46 changed files with 1,116 additions and 71 deletions.
98 changes: 98 additions & 0 deletions ui/app/adapters/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { assign } from '@ember/polyfills';
import { allSettled } from 'rsvp';
import ApplicationAdapter from './application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default ApplicationAdapter.extend({
namespace: 'v1',

createOrUpdate(store, type, snapshot) {
const serializer = store.serializerFor(type.modelName);
const data = serializer.serialize(snapshot);
const { id } = snapshot;
let url = this.urlForTransformations(snapshot.record.get('backend'), id);

return this.ajax(url, 'POST', { data });
},

createRecord() {
return this.createOrUpdate(...arguments);
},

updateRecord() {
return this.createOrUpdate(...arguments, 'update');
},

deleteRecord(store, type, snapshot) {
const { id } = snapshot;
return this.ajax(this.urlForTransformations(snapshot.record.get('backend'), id), 'DELETE');
},

pathForType() {
return 'transform';
},

urlForTransformations(backend, id) {
let url = `${this.buildURL()}/${encodePath(backend)}/transformation`;
if (id) {
url = url + '/' + encodePath(id);
}
return url;
},

optionsForQuery(id) {
let data = {};
if (!id) {
data['list'] = true;
}
return { data };
},

fetchByQuery(store, query) {
const { id, backend } = query;
const queryAjax = this.ajax(this.urlForTransformations(backend, id), 'GET', this.optionsForQuery(id));

return allSettled([queryAjax]).then(results => {
// query result 404d, so throw the adapterError
if (!results[0].value) {
throw results[0].reason;
}
let resp = {
id,
name: id,
backend,
data: {},
};

results.forEach(result => {
if (result.value) {
if (result.value.data.roles) {
// TODO: Check if this is needed and remove if not
resp.data = assign({}, resp.data, { zero_address_roles: result.value.data.roles });
} else {
let d = result.value.data;
if (d.templates) {
// In Transformations data goes up as "template", but comes down as "templates"
// To keep the keys consistent we're translating here
d = {
...d,
template: [d.templates],
};
delete d.templates;
}
resp.data = assign({}, resp.data, d);
}
}
});
return resp;
});
},

query(store, type, query) {
return this.fetchByQuery(store, query);
},

queryRecord(store, type, query) {
return this.fetchByQuery(store, query);
},
});
25 changes: 25 additions & 0 deletions ui/app/adapters/transform/role.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ApplicationAdapater from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default ApplicationAdapater.extend({
namespace: 'v1',

pathForType() {
return 'role';
},

_url(backend, id) {
let type = this.pathForType();
let base = `/v1/${encodePath(backend)}/${type}`;
if (id) {
return `${base}/${encodePath(id)}`;
}
return base + '?list=true';
},

query(store, type, query) {
return this.ajax(this._url(query.backend), 'GET').then(result => {
return result;
});
},
});
25 changes: 25 additions & 0 deletions ui/app/adapters/transform/template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ApplicationAdapater from '../application';
import { encodePath } from 'vault/utils/path-encoding-helpers';

export default ApplicationAdapater.extend({
namespace: 'v1',

pathForType() {
return 'template';
},

_url(backend, id) {
let type = this.pathForType();
let base = `${this.buildURL()}/${encodePath(backend)}/${type}`;
if (id) {
return `${base}/${encodePath(id)}`;
}
return base + '?list=true';
},

query(store, type, query) {
return this.ajax(this._url(query.backend), 'GET').then(result => {
return result;
});
},
});
9 changes: 4 additions & 5 deletions ui/app/components/mount-backend-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computed } from '@ember/object';
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { methods } from 'vault/helpers/mountable-auth-methods';
import { engines, KMIP } from 'vault/helpers/mountable-secret-engines';
import { engines, KMIP, TRANSFORM } from 'vault/helpers/mountable-secret-engines';

const METHODS = methods();
const ENGINES = engines();
Expand Down Expand Up @@ -56,11 +56,10 @@ export default Component.extend({
}),

engines: computed('version.features[]', function() {
if (this.version.hasFeature('KMIP')) {
return ENGINES.concat([KMIP]);
} else {
return ENGINES;
if (this.get('version.isEnterprise')) {
return ENGINES.concat([KMIP, TRANSFORM]);
}
return ENGINES;
}),

willDestroy() {
Expand Down
3 changes: 3 additions & 0 deletions ui/app/components/transform-create-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransformBase from './transform-edit-base';

export default TransformBase.extend({});
104 changes: 104 additions & 0 deletions ui/app/components/transform-edit-base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { inject as service } from '@ember/service';
import { or } from '@ember/object/computed';
import { isBlank } from '@ember/utils';
import { task, waitForEvent } from 'ember-concurrency';
import Component from '@ember/component';
import { set, get } from '@ember/object';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import keys from 'vault/lib/keycodes';

const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
const SHOW_ROUTE = 'vault.cluster.secrets.backend.show';

export default Component.extend(FocusOnInsertMixin, {
router: service(),
wizard: service(),

mode: null,
// TODO: Investigate if we need all of these
emptyData: '{\n}',
onDataChange() {},
onRefresh() {},
model: null,
requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'),

init() {
this._super(...arguments);
this.set('backendType', 'transform');
},

willDestroyElement() {
this._super(...arguments);
if (this.model && this.model.isError) {
this.model.rollbackAttributes();
}
},

waitForKeyUp: task(function*() {
while (true) {
let event = yield waitForEvent(document.body, 'keyup');
this.onEscape(event);
}
})
.on('didInsertElement')
.cancelOn('willDestroyElement'),

transitionToRoute() {
this.get('router').transitionTo(...arguments);
},

onEscape(e) {
if (e.keyCode !== keys.ESC || this.get('mode') !== 'show') {
return;
}
this.transitionToRoute(LIST_ROOT_ROUTE);
},

hasDataChanges() {
get(this, 'onDataChange')(get(this, 'model.hasDirtyAttributes'));
},

persist(method, successCallback) {
const model = get(this, 'model');
return model[method]().then(() => {
if (!get(model, 'isError')) {
if (this.get('wizard.featureState') === 'role') {
this.get('wizard').transitionFeatureMachine('role', 'CONTINUE', this.get('backendType'));
}
successCallback(model);
}
});
},

actions: {
createOrUpdate(type, event) {
event.preventDefault();
const modelId = this.get('model.id') || this.get('model.name'); // transform comes in as model.name
// prevent from submitting if there's no key
// maybe do something fancier later
if (type === 'create' && isBlank(modelId)) {
return;
}

this.persist('save', () => {
this.hasDataChanges();
this.transitionToRoute(SHOW_ROUTE, modelId);
});
},

setValue(key, event) {
set(get(this, 'model'), key, event.target.checked);
},

refresh() {
this.get('onRefresh')();
},

delete() {
this.persist('destroyRecord', () => {
this.hasDataChanges();
this.transitionToRoute(LIST_ROOT_ROUTE);
});
},
},
});
3 changes: 3 additions & 0 deletions ui/app/components/transform-edit-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransformBase from './transform-edit-base';

export default TransformBase.extend({});
3 changes: 3 additions & 0 deletions ui/app/components/transform-show-transformation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransformBase from './transform-edit-base';

export default TransformBase.extend({});
3 changes: 3 additions & 0 deletions ui/app/components/transformation-edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import TransformBase from './transform-edit-base';

export default TransformBase.extend({});
9 changes: 9 additions & 0 deletions ui/app/helpers/mountable-secret-engines.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ export const KMIP = {
value: 'kmip',
type: 'kmip',
category: 'generic',
requiredFeature: 'KMIP',
};

export const TRANSFORM = {
displayName: 'Transform',
value: 'transform',
type: 'transform',
category: 'generic',
requiredFeature: 'Transform Secrets Engine',
};

const MOUNTABLE_SECRET_ENGINES = [
Expand Down
49 changes: 49 additions & 0 deletions ui/app/helpers/options-for-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,55 @@ const SECRET_BACKENDS = {
editComponent: 'role-ssh-edit',
listItemPartial: 'partials/secret-list/ssh-role-item',
},
transform: {
displayName: 'Transformation',
navigateTree: false,
listItemPartial: 'partials/secret-list/transform-transformation-item',
tabs: [
{
name: 'transformations',
label: 'Transformations',
searchPlaceholder: 'Filter transformations',
item: 'transformation',
create: 'Create transformation',
editComponent: 'transformation-edit',
},
// TODO: Add tabs as needed
// {
// name: 'roles',
// modelPrefix: 'role/',
// label: 'Roles',
// searchPlaceholder: 'Filter roles',
// item: 'roles',
// create: 'Create role',
// tab: 'role',
// listItemPartial: 'partials/secret-list/item',
// editComponent: 'transform-role-edit',
// },
// {
// name: 'templates',
// modelPrefix: 'template/',
// label: 'Templates',
// searchPlaceholder: 'Filter templates',
// item: 'templates',
// create: 'Create template',
// tab: 'template',
// listItemPartial: 'partials/secret-list/item',
// editComponent: 'transform-template-edit',
// },
// {
// name: 'alphabets',
// modelPrefix: 'alphabet/',
// label: 'Alphabets',
// searchPlaceholder: 'Filter alphabets',
// item: 'alphabets',
// create: 'Create alphabet',
// tab: 'alphabet',
// listItemPartial: 'partials/secret-list/item',
// editComponent: 'alphabet-edit',
// },
],
},
transit: {
searchPlaceholder: 'Filter keys',
item: 'key',
Expand Down
12 changes: 11 additions & 1 deletion ui/app/helpers/supported-secret-backends.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { helper as buildHelper } from '@ember/component/helper';

const SUPPORTED_SECRET_BACKENDS = ['aws', 'cubbyhole', 'generic', 'kv', 'pki', 'ssh', 'transit', 'kmip'];
const SUPPORTED_SECRET_BACKENDS = [
'aws',
'cubbyhole',
'generic',
'kv',
'pki',
'ssh',
'transit',
'kmip',
'transform',
];

export function supportedSecretBackends() {
return SUPPORTED_SECRET_BACKENDS;
Expand Down
Loading

0 comments on commit 6fb9768

Please sign in to comment.