vault/ui/tests/integration/components/transit-key-actions-test.js
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

348 lines
11 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { run } from '@ember/runloop';
import { resolve } from 'rsvp';
import { assign } from '@ember/polyfills';
import Service from '@ember/service';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, find, findAll, fillIn, blur, triggerEvent } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { encodeString } from 'vault/utils/b64';
import waitForError from 'vault/tests/helpers/wait-for-error';
const storeStub = Service.extend({
callArgs: null,
keyActionReturnVal: null,
rootKeyActionReturnVal: null,
adapterFor() {
const self = this;
return {
keyAction(action, { backend, id, payload }, options) {
self.set('callArgs', { action, backend, id, payload });
self.set('callArgsOptions', options);
const rootResp = assign({}, self.get('rootKeyActionReturnVal'));
const resp =
Object.keys(rootResp).length > 0
? rootResp
: {
data: assign({}, self.get('keyActionReturnVal')),
};
return resolve(resp);
},
};
},
});
module('Integration | Component | transit key actions', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
run(() => {
this.owner.unregister('service:store');
this.owner.register('service:store', storeStub);
this.storeService = this.owner.lookup('service:store');
});
});
test('it requires `key`', async function (assert) {
const promise = waitForError();
render(hbs`
{{transit-key-actions}}
<div id="modal-wormhole"></div>
`);
const err = await promise;
assert.ok(err.message.includes('`key` is required for'), 'asserts without key');
});
test('it renders', async function (assert) {
this.set('key', { backend: 'transit', supportedActions: ['encrypt'] });
await render(hbs`
{{transit-key-actions selectedAction="encrypt" key=this.key}}
<div id="modal-wormhole"></div>
`);
assert.dom('[data-test-transit-action="encrypt"]').exists({ count: 1 }, 'renders encrypt');
this.set('key', { backend: 'transit', supportedActions: ['sign'] });
await render(hbs`
{{transit-key-actions selectedAction="sign" key=this.key}}
<div id="modal-wormhole"></div>`);
assert.dom('[data-test-transit-action="sign"]').exists({ count: 1 }, 'renders sign');
});
test('it renders: signature_algorithm field', async function (assert) {
this.set('key', { backend: 'transit', supportsSigning: true, supportedActions: ['sign', 'verify'] });
this.set('selectedAction', 'sign');
await render(hbs`
{{transit-key-actions selectedAction=this.selectedAction key=this.key}}
<div id="modal-wormhole"></div>
`);
assert
.dom('[data-test-signature-algorithm]')
.doesNotExist('does not render signature_algorithm field on sign');
this.set('selectedAction', 'verify');
assert
.dom('[data-test-signature-algorithm]')
.doesNotExist('does not render signature_algorithm field on verify');
this.set('selectedAction', 'sign');
this.set('key', {
type: 'rsa-2048',
supportsSigning: true,
backend: 'transit',
supportedActions: ['sign', 'verify'],
});
assert
.dom('[data-test-signature-algorithm]')
.exists({ count: 1 }, 'renders signature_algorithm field on sign with rsa key');
this.set('selectedAction', 'verify');
assert
.dom('[data-test-signature-algorithm]')
.exists({ count: 1 }, 'renders signature_algorithm field on verify with rsa key');
});
test('it renders: rotate', async function (assert) {
this.set('key', { backend: 'transit', id: 'akey', supportedActions: ['rotate'] });
await render(hbs`
{{transit-key-actions selectedAction="rotate" key=this.key}}
<div id="modal-wormhole"></div>
`);
assert.dom('*').hasText('', 'renders an empty div');
this.set('key.canRotate', true);
assert
.dom('button')
.hasText('Rotate encryption key', 'renders confirm-button when key.canRotate is true');
});
async function doEncrypt(assert, actions = [], keyattrs = {}) {
const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat(actions) };
const key = assign({}, keyDefaults, keyattrs);
this.set('key', key);
this.set('selectedAction', 'encrypt');
this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
await render(hbs`
{{transit-key-actions selectedAction=this.selectedAction key=this.key}}
<div id="modal-wormhole"></div>
`);
find('#plaintext-control .CodeMirror').CodeMirror.setValue('plaintext');
await click('button[type="submit"]');
assert.deepEqual(
this.storeService.callArgs,
{
action: 'encrypt',
backend: 'transit',
id: 'akey',
payload: {
plaintext: encodeString('plaintext'),
},
},
'passes expected args to the adapter'
);
assert.strictEqual(find('[data-test-encrypted-value="ciphertext"]').innerText, 'secret');
// exit modal
await click('[data-test-modal-background]');
// Encrypt again, with pre-encoded value and checkbox selected
const preEncodedValue = encodeString('plaintext');
find('#plaintext-control .CodeMirror').CodeMirror.setValue(preEncodedValue);
await click('input[data-test-transit-input="encodedBase64"]');
await click('button[type="submit"]');
assert.deepEqual(
this.storeService.callArgs,
{
action: 'encrypt',
backend: 'transit',
id: 'akey',
payload: {
plaintext: preEncodedValue,
},
},
'passes expected args to the adapter'
);
}
test('it encrypts', doEncrypt);
test('it shows key version selection', async function (assert) {
const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat([]) };
const keyattrs = { keysForEncryption: [3, 2, 1], latestVersion: 3 };
const key = assign({}, keyDefaults, keyattrs);
this.set('key', key);
this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
await render(hbs`
{{transit-key-actions selectedAction="encrypt" key=this.key}}
<div id="modal-wormhole"></div>
`);
findAll('.CodeMirror')[0].CodeMirror.setValue('plaintext');
assert.dom('#key_version').exists({ count: 1 }, 'it renders the key version selector');
await triggerEvent('#key_version', 'change');
await click('button[type="submit"]');
assert.deepEqual(
this.storeService.callArgs,
{
action: 'encrypt',
backend: 'transit',
id: 'akey',
payload: {
plaintext: encodeString('plaintext'),
key_version: '0',
},
},
'includes key_version in the payload'
);
});
test('it hides key version selection', async function (assert) {
const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat([]) };
const keyattrs = { keysForEncryption: [1] };
const key = assign({}, keyDefaults, keyattrs);
this.set('key', key);
this.set('storeService.keyActionReturnVal', { ciphertext: 'secret' });
await render(hbs`
{{transit-key-actions selectedAction="encrypt" key=this.key}}
<div id="modal-wormhole"></div>
`);
// await fillIn('#plaintext', 'plaintext');
find('#plaintext-control .CodeMirror').CodeMirror.setValue('plaintext');
assert.dom('#key_version').doesNotExist('it does not render the selector when there is only one key');
});
test('it does not carry ciphertext value over to decrypt', async function (assert) {
assert.expect(4);
const plaintext = 'not so secret';
await doEncrypt.call(this, assert, ['decrypt']);
this.set('storeService.keyActionReturnVal', { plaintext });
this.set('selectedAction', 'decrypt');
assert.strictEqual(
find('#ciphertext-control .CodeMirror').CodeMirror.getValue(),
'',
'does not prefill ciphertext value'
);
});
const setupExport = async function () {
this.set('key', {
backend: 'transit',
id: 'akey',
supportedActions: ['export'],
exportKeyTypes: ['encryption'],
validKeyVersions: [1],
});
await render(hbs`
{{transit-key-actions key=this.key}}
<div id="modal-wormhole"></div>
`);
};
test('it can export a key:default behavior', async function (assert) {
this.set('storeService.rootKeyActionReturnVal', { wrap_info: { token: 'wrapped-token' } });
await setupExport.call(this);
await click('button[type="submit"]');
assert.deepEqual(
this.storeService.callArgs,
{
action: 'export',
backend: 'transit',
id: 'akey',
payload: {
param: ['encryption'],
},
},
'passes expected args to the adapter'
);
assert.strictEqual(this.storeService.callArgsOptions.wrapTTL, '30m', 'passes value for wrapTTL');
assert.strictEqual(
find('[data-test-encrypted-value="export"]').innerText,
'wrapped-token',
'wraps by default'
);
});
test('it can export a key:unwrapped behavior', async function (assert) {
const response = { keys: { a: 'key' } };
this.set('storeService.keyActionReturnVal', response);
await setupExport.call(this);
await click('[data-test-toggle-label="Wrap response"]');
await click('button[type="submit"]');
assert.dom('.modal.is-active').exists('Modal opens after export');
assert.deepEqual(
find('.modal [data-test-encrypted-value="export"]').innerText,
JSON.stringify(response, null, 2),
'prints json response'
);
});
test('it can export a key: unwrapped, single version', async function (assert) {
const response = { keys: { a: 'key' } };
this.set('storeService.keyActionReturnVal', response);
await setupExport.call(this);
await click('[data-test-toggle-label="Wrap response"]');
await click('#exportVersion');
await triggerEvent('#exportVersion', 'change');
await click('button[type="submit"]');
assert.dom('.modal.is-active').exists('Modal opens after export');
assert.deepEqual(
find('.modal [data-test-encrypted-value="export"]').innerText,
JSON.stringify(response, null, 2),
'prints json response'
);
assert.deepEqual(
this.storeService.callArgs,
{
action: 'export',
backend: 'transit',
id: 'akey',
payload: {
param: ['encryption', 1],
},
},
'passes expected args to the adapter'
);
});
test('it includes algorithm param for HMAC', async function (assert) {
this.set('key', {
backend: 'transit',
id: 'akey',
supportedActions: ['hmac'],
validKeyVersions: [1],
});
await render(hbs`
{{transit-key-actions key=this.key}}
<div id="modal-wormhole"></div>
`);
await fillIn('#algorithm', 'sha2-384');
await blur('#algorithm');
await fillIn('[data-test-component="code-mirror-modifier"] textarea', 'plaintext');
await click('input[data-test-transit-input="encodedBase64"]');
await click('button[type="submit"]');
assert.deepEqual(
this.storeService.callArgs,
{
action: 'hmac',
backend: 'transit',
id: 'akey',
payload: {
algorithm: 'sha2-384',
input: 'plaintext',
},
},
'passes expected args to the adapter'
);
});
});