mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
UI: add deletion_allowed to transform, add tokenization transform type (#25436)
* update adapter to accept :type in url * update model attributes to include deletion_allowed and tokenization type * update max_ttl text * update adapter test * add changelog; * update comment
This commit is contained in:
3
changelog/25436.txt
Normal file
3
changelog/25436.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Add `deletion_allowed` param to transformations and include `tokenization` as a type option
|
||||
```
|
||||
@@ -11,11 +11,11 @@ import { encodePath } from 'vault/utils/path-encoding-helpers';
|
||||
export default ApplicationAdapter.extend({
|
||||
namespace: 'v1',
|
||||
|
||||
createOrUpdate(store, type, snapshot) {
|
||||
const { backend, name } = snapshot.record;
|
||||
const serializer = store.serializerFor(type.modelName);
|
||||
createOrUpdate(store, { modelName }, snapshot) {
|
||||
const { backend, name, type } = snapshot.record;
|
||||
const serializer = store.serializerFor(modelName);
|
||||
const data = serializer.serialize(snapshot);
|
||||
const url = this.urlForTransformations(backend, name);
|
||||
const url = this.urlForTransformations(backend, name, type);
|
||||
|
||||
return this.ajax(url, 'POST', { data }).then((resp) => {
|
||||
const response = resp || {};
|
||||
@@ -41,11 +41,11 @@ export default ApplicationAdapter.extend({
|
||||
return 'transform';
|
||||
},
|
||||
|
||||
urlForTransformations(backend, id) {
|
||||
let url = `${this.buildURL()}/${encodePath(backend)}/transformation`;
|
||||
if (id) {
|
||||
url = url + '/' + encodePath(id);
|
||||
}
|
||||
urlForTransformations(backend, id, type) {
|
||||
const base = `${this.buildURL()}/${encodePath(backend)}`;
|
||||
// when type exists, transformations is plural
|
||||
const url = type ? `${base}/transformations/${type}` : `${base}/transformation`;
|
||||
if (id) return `${url}/${encodePath(id)}`;
|
||||
return url;
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@ export default ApplicationAdapter.extend({
|
||||
const queryAjax = this.ajax(this.urlForTransformations(backend, id), 'GET', this.optionsForQuery(id));
|
||||
|
||||
return allSettled([queryAjax]).then((results) => {
|
||||
// query result 404d, so throw the adapterError
|
||||
// query result 404, so throw the adapterError
|
||||
if (!results[0].value) {
|
||||
throw results[0].reason;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ import { computed } from '@ember/object';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
|
||||
// these arrays define the order in which the fields will be displayed
|
||||
// see
|
||||
// these arrays define the order in which the fields will be displayed, see:
|
||||
// https://developer.hashicorp.com/vault/api-docs/secret/transform#create-update-transformation-deprecated-1-6
|
||||
const TYPES = [
|
||||
{
|
||||
@@ -20,6 +19,10 @@ const TYPES = [
|
||||
value: 'masking',
|
||||
displayName: 'Masking',
|
||||
},
|
||||
{
|
||||
value: 'tokenization',
|
||||
displayName: 'Tokenization',
|
||||
},
|
||||
];
|
||||
|
||||
const TWEAK_SOURCE = [
|
||||
@@ -83,12 +86,49 @@ export default Model.extend({
|
||||
subText: 'Search for an existing role, type a new role to create it, or use a wildcard (*).',
|
||||
wildcardLabel: 'role',
|
||||
}),
|
||||
transformAttrs: computed('type', function () {
|
||||
if (this.type === 'masking') {
|
||||
return ['name', 'type', 'masking_character', 'template', 'allowed_roles'];
|
||||
}
|
||||
return ['name', 'type', 'tweak_source', 'template', 'allowed_roles'];
|
||||
deletion_allowed: attr('boolean', {
|
||||
label: 'Allow deletion',
|
||||
subText:
|
||||
'If checked, this transform can be deleted otherwise deletion is blocked. Note that deleting the transform deletes the underlying key which makes decoding of tokenized values impossible without restoring from a backup.',
|
||||
}),
|
||||
convergent: attr('boolean', {
|
||||
label: 'Use convergent tokenization',
|
||||
subText:
|
||||
"This cannot be edited later. If checked, tokenization of the same plaintext more than once results in the same token. Defaults to false as unique tokens are more desirable from a security standpoint if there isn't a use-case need for convergence.",
|
||||
}),
|
||||
stores: attr('array', {
|
||||
label: 'Stores',
|
||||
editType: 'stringArray',
|
||||
subText:
|
||||
"The list of tokenization stores to use for tokenization state. Vault's internal storage is used by default.",
|
||||
}),
|
||||
mapping_mode: attr('string', {
|
||||
defaultValue: 'default',
|
||||
subText:
|
||||
'Specifies the mapping mode for stored tokenization values. "default" is strongly recommended for highest security, "exportable" allows for all plaintexts to be decoded via the export-decoded endpoint in an emergency.',
|
||||
}),
|
||||
max_ttl: attr({
|
||||
editType: 'ttl',
|
||||
defaultValue: '0',
|
||||
label: 'Maximum TTL (time-to-live) of a token',
|
||||
helperTextDisabled: 'If "0" or unspecified, tokens may have no expiration.',
|
||||
}),
|
||||
|
||||
transformAttrs: computed('type', function () {
|
||||
// allowed_roles not included so it displays at the bottom of the form
|
||||
const baseAttrs = ['name', 'type', 'deletion_allowed'];
|
||||
switch (this.type) {
|
||||
case 'fpe':
|
||||
return [...baseAttrs, 'tweak_source', 'template', 'allowed_roles'];
|
||||
case 'masking':
|
||||
return [...baseAttrs, 'masking_character', 'template', 'allowed_roles'];
|
||||
case 'tokenization':
|
||||
return [...baseAttrs, 'mapping_mode', 'convergent', 'max_ttl', 'stores', 'allowed_roles'];
|
||||
default:
|
||||
return [...baseAttrs];
|
||||
}
|
||||
}),
|
||||
|
||||
transformFieldAttrs: computed('transformAttrs', function () {
|
||||
return expandAttributeMeta(this, this.transformAttrs);
|
||||
}),
|
||||
|
||||
95
ui/tests/unit/adapters/transform-test.js
Normal file
95
ui/tests/unit/adapters/transform-test.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
|
||||
const TRANSFORM_TYPES = ['fpe', 'masking', 'tokenization'];
|
||||
module('Unit | Adapter | transform', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.backend = 'my-transform-engine';
|
||||
this.name = 'my-transform';
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
this.store.unloadAll('transform');
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint when querying all records', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.server.get(`${this.backend}/transformation`, (schema, req) => {
|
||||
assert.ok(true, 'GET request made to correct endpoint when querying record');
|
||||
assert.propEqual(req.queryParams, { list: 'true' }, 'query params include list: true');
|
||||
return { data: { key_info: {}, keys: [] } };
|
||||
});
|
||||
await this.store.query('transform', { backend: this.backend });
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint when querying a record', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.get(`${this.backend}/transformation/${this.name}`, () => {
|
||||
assert.ok(true, 'GET request made to correct endpoint when querying record');
|
||||
return { data: { backend: this.backend, name: this.name } };
|
||||
});
|
||||
await this.store.queryRecord('transform', { backend: this.backend, id: this.name });
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint when creating new record', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
for (const type of TRANSFORM_TYPES) {
|
||||
const name = `transform-${type}-test`;
|
||||
this.server.post(`${this.backend}/transformations/${type}/${name}`, () => {
|
||||
assert.ok(true, `POST request made to transformations/${type}/:name creating a record`);
|
||||
return { data: { backend: this.backend, name, type } };
|
||||
});
|
||||
const record = this.store.createRecord('transform', { backend: this.backend, name, type });
|
||||
await record.save();
|
||||
}
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint when updating record', async function (assert) {
|
||||
assert.expect(3);
|
||||
for (const type of TRANSFORM_TYPES) {
|
||||
const name = `transform-${type}-test`;
|
||||
this.server.post(`${this.backend}/transformations/${type}/${name}`, () => {
|
||||
assert.ok(true, `POST request made to transformations/${type}/:name endpoint`);
|
||||
});
|
||||
this.store.pushPayload('transform', {
|
||||
modelName: 'transform',
|
||||
backend: this.backend,
|
||||
id: name,
|
||||
type,
|
||||
name,
|
||||
});
|
||||
const record = this.store.peekRecord('transform', name);
|
||||
await record.save();
|
||||
}
|
||||
});
|
||||
|
||||
test('it should make request to correct endpoint when deleting record', async function (assert) {
|
||||
assert.expect(3);
|
||||
for (const type of TRANSFORM_TYPES) {
|
||||
const name = `transform-${type}-test`;
|
||||
this.server.delete(`${this.backend}/transformation/${name}`, () => {
|
||||
assert.ok(true, `type: ${type} - DELETE request to transformation/:name endpoint`);
|
||||
});
|
||||
this.store.pushPayload('transform', {
|
||||
modelName: 'transform',
|
||||
backend: this.backend,
|
||||
id: name,
|
||||
type,
|
||||
name,
|
||||
});
|
||||
const record = this.store.peekRecord('transform', name);
|
||||
await record.destroyRecord();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user