vault/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
2024-04-10 22:01:35 +00:00

1205 lines
56 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { v4 as uuidv4 } from 'uuid';
import { click, currentURL, fillIn, typeIn, visit } from '@ember/test-helpers';
import { setupApplicationTest } from 'vault/tests/helpers';
import authPage from 'vault/tests/pages/auth';
import { deleteEngineCmd, mountEngineCmd, runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
import { personas } from 'vault/tests/helpers/kv/policy-generator';
import { clearRecords, writeVersionedSecret } from 'vault/tests/helpers/kv/kv-run-commands';
import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
import { grantAccessForWrite, setupControlGroup } from 'vault/tests/helpers/control-groups';
/**
* This test set is for testing the flow for creating new secrets and versions.
* Letter(s) in parenthesis at the end are shorthand for the persona,
* for ease of tracking down specific tests failures from CI
*/
module('Acceptance | kv-v2 workflow | secret and version create', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(async function () {
this.backend = `kv-create-${uuidv4()}`;
this.store = this.owner.lookup('service:store');
await authPage.login();
await runCmd(mountEngineCmd('kv-v2', this.backend), false);
await writeVersionedSecret(this.backend, 'app/first', 'foo', 'bar', 2);
});
hooks.afterEach(async function () {
await authPage.login();
return runCmd(deleteEngineCmd(this.backend));
});
module('admin persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(tokenWithPolicyCmd(`admin-${this.backend}`, personas.admin(this.backend)));
await authPage.login(token);
clearRecords(this.store);
return;
});
test('cancel on create clears model (a)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'single secret exists on list');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'jk');
await click(FORM.cancelBtn);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'psych');
await click(PAGE.breadcrumbAtIdx(1));
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
});
test('cancel on new version rolls back model (a)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`);
assert.dom(PAGE.infoRowValue('foo')).exists('key has expected value');
await click(PAGE.detail.createNewVersion);
await fillIn(FORM.keyInput(), 'bar');
await click(FORM.cancelBtn);
assert.dom(PAGE.infoRowValue('foo')).exists('secret is previous value');
await click(PAGE.detail.createNewVersion);
await fillIn(FORM.keyInput(), 'bar');
await click(PAGE.breadcrumbAtIdx(3));
assert.dom(PAGE.infoRowValue('foo')).exists('secret is previous value');
});
test('create & update root secret with default metadata (a)', async function (assert) {
const backend = this.backend;
const secretPath = 'some secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert
.dom(FORM.validationWarning)
.hasText(
"Path contains whitespace. If this is desired, you'll need to encode it with %20 in API requests."
);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
// Submit with API errors
await click(FORM.saveBtn);
assert.dom(FORM.messageError).hasText('Error no data provided', 'API error shows on form');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
// Details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=1`,
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
assert
.dom(PAGE.infoRowValue('Delete version after'))
.hasText('Never delete', 'Delete version after has default 0s');
// Add new version
await click(PAGE.secretTab('Secret'));
await click(PAGE.detail.createNewVersion);
assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
assert.dom(FORM.inputByAttr('path')).hasValue(secretPath);
assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
assert.dom(FORM.keyInput()).hasValue('api_key');
assert.dom(FORM.maskedValueInput()).hasValue('partyparty');
await fillIn(FORM.keyInput(1), 'api_url');
await fillIn(FORM.maskedValueInput(1), 'hashicorp.com');
await click(FORM.saveBtn);
// Back to details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=2`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
assert.dom(PAGE.infoRow).exists({ count: 2 }, '2 rows of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
assert.dom(PAGE.infoRowValue('api_url')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
await click(PAGE.infoRowToggleMasked('api_url'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
assert.dom(PAGE.infoRowValue('api_url')).hasText('hashicorp.com', 'secret value shows after toggle');
});
test('create nested secret with metadata (a)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret
await typeIn(FORM.inputByAttr('path'), 'my/');
assert.dom(FORM.validation('path')).hasText("Path can't end in forward slash '/'.");
await typeIn(FORM.inputByAttr('path'), 'secret');
assert.dom(FORM.validation('path')).doesNotExist('form validation goes away');
await fillIn(FORM.keyInput(), 'password');
await fillIn(FORM.maskedValueInput(), 'kittens1234');
await click(FORM.toggleMetadata);
assert.dom(PAGE.create.metadataSection).exists('Shows metadata section after toggled');
// Check initial values
assert.dom(FORM.inputByAttr('maxVersions')).hasValue('0');
assert.dom(FORM.inputByAttr('casRequired')).isNotChecked();
assert.dom(FORM.toggleByLabel('Automate secret deletion')).isNotChecked();
// MaxVersions validation
await fillIn(FORM.inputByAttr('maxVersions'), 'seven');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('Maximum versions must be a number.');
await fillIn(FORM.inputByAttr('maxVersions'), '99999999999999999');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('You cannot go over 16 characters.');
await fillIn(FORM.inputByAttr('maxVersions'), '7');
// Fill in other metadata
await click(FORM.inputByAttr('casRequired'));
await click(FORM.toggleByLabel('Automate secret deletion'));
await fillIn(FORM.ttlValue('Automate secret deletion'), '1000');
// Fill in custom metadata
await fillIn(`${PAGE.create.metadataSection} ${FORM.keyInput()}`, 'team');
await fillIn(`${PAGE.create.metadataSection} ${FORM.valueInput()}`, 'UI');
// Fill in metadata
await click(FORM.saveBtn);
// Details
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('my/secret')}/details?version=1`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('password')).hasText('***********');
await click(PAGE.infoRowToggleMasked('password'));
assert.dom(PAGE.infoRowValue('password')).hasText('kittens1234', 'secret value shows after toggle');
// Metadata
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 1 }, 'One custom metadata row shows');
assert.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.infoRowValue('team')}`).hasText('UI');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('7', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('Yes', 'cas enforced');
assert
.dom(PAGE.infoRowValue('Delete version after'))
.hasText('16 minutes 40 seconds', 'Delete version after has custom value');
});
test('creates a secret at a sub-directory (a)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list/app/`);
assert.dom(PAGE.list.item('first')).exists('Lists first sub-secret');
assert.dom(PAGE.list.item('new')).doesNotExist('Does not show new secret');
await click(PAGE.list.createSecret);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/create?initialKey=app%2F`,
'Goes to create page with initialKey'
);
await typeIn(FORM.inputByAttr('path'), 'new');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('app/new')}/details?version=1`,
'Redirects to detail after save'
);
await click(PAGE.breadcrumbAtIdx(2));
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`, 'sub-dir page');
assert.dom(PAGE.list.item('new')).exists('Lists new secret in sub-dir');
});
test('create new version of secret from older version (a)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
await click(PAGE.detail.versionDropdown);
await click(`${PAGE.detail.version(1)} a`);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=1`,
'goes to version 1'
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
await click(PAGE.detail.createNewVersion);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details/edit?version=1`,
'Goes to new version page'
);
assert
.dom(FORM.versionAlert)
.hasText(
'Warning You are creating a new version based on data from Version 1. The current version for app/first is Version 2.',
'Shows version warning'
);
assert.dom(FORM.keyInput()).hasValue('key-1', 'Key input has old value');
assert.dom(FORM.maskedValueInput()).hasValue('val-1', 'Val input has old value');
await fillIn(FORM.keyInput(), 'my-key');
await fillIn(FORM.maskedValueInput(), 'my-value');
await click(FORM.saveBtn);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=3`,
'goes to latest version 3'
);
await click(PAGE.infoRowToggleMasked('my-key'));
assert.dom(PAGE.infoRowValue('my-key')).hasText('my-value', 'has new value');
});
});
module('data-reader persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(
tokenWithPolicyCmd(`data-reader-${this.backend}`, personas.dataReader(this.backend))
);
await authPage.login(token);
clearRecords(this.store);
return;
});
test('cancel on create clears model (dr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
assert.dom(PAGE.list.item()).doesNotExist('list view has no items');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'jk');
await click(FORM.cancelBtn);
assert.dom(PAGE.list.item()).doesNotExist('list view still has no items');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'psych');
await click(PAGE.breadcrumbAtIdx(1));
assert.dom(PAGE.list.item()).doesNotExist('list view still has no items');
});
test('cancel on new version rolls back model (dr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`);
assert.dom(PAGE.infoRowValue('foo')).exists('key has expected value');
assert.dom(PAGE.detail.createNewVersion).doesNotExist();
});
test('create & update root secret with default metadata (dr)', async function (assert) {
const backend = this.backend;
const secretPath = 'some secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert
.dom(FORM.validationWarning)
.hasText(
"Path contains whitespace. If this is desired, you'll need to encode it with %20 in API requests."
);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
// Submit with API errors
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('foo')).hasText('***********');
await click(PAGE.infoRowToggleMasked('foo'));
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'secret value shows after toggle');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('You do not have access to secret metadata', 'shows no access state on metadata');
// Add new version
await click(PAGE.secretTab('Secret'));
assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version');
});
test('create nested secret with metadata (dr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret
await typeIn(FORM.inputByAttr('path'), 'my/');
assert.dom(FORM.validation('path')).hasText("Path can't end in forward slash '/'.");
await typeIn(FORM.inputByAttr('path'), 'secret');
assert.dom(FORM.validation('path')).doesNotExist('form validation goes away');
await fillIn(FORM.keyInput(), 'password');
await fillIn(FORM.maskedValueInput(), 'kittens1234');
await click(FORM.toggleMetadata);
assert.dom(PAGE.create.metadataSection).exists('Shows metadata section after toggled');
// Check initial values
assert.dom(FORM.inputByAttr('maxVersions')).hasValue('0');
assert.dom(FORM.inputByAttr('casRequired')).isNotChecked();
assert.dom(FORM.toggleByLabel('Automate secret deletion')).isNotChecked();
// MaxVersions validation
await fillIn(FORM.inputByAttr('maxVersions'), 'seven');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('Maximum versions must be a number.');
await fillIn(FORM.inputByAttr('maxVersions'), '99999999999999999');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('You cannot go over 16 characters.');
await fillIn(FORM.inputByAttr('maxVersions'), '7');
// Fill in other metadata
await click(FORM.inputByAttr('casRequired'));
await click(FORM.toggleByLabel('Automate secret deletion'));
await fillIn(FORM.ttlValue('Automate secret deletion'), '1000');
// Fill in custom metadata
await fillIn(`${PAGE.create.metadataSection} ${FORM.keyInput()}`, 'team');
await fillIn(`${PAGE.create.metadataSection} ${FORM.valueInput()}`, 'UI');
// Fill in metadata
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('creates a secret at a sub-directory (dr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list/app/`);
assert.dom(PAGE.list.item()).doesNotExist('Does not list any secrets');
await click(PAGE.list.createSecret);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/create?initialKey=app%2F`,
'Goes to create page with initialKey'
);
await typeIn(FORM.inputByAttr('path'), 'new');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('create new version of secret from older version (dr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=1`);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('version dropdown does not show');
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version');
});
});
module('data-list-reader persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(
tokenWithPolicyCmd(`data-list-reader-${this.backend}`, personas.dataListReader(this.backend))
);
await authPage.login(token);
clearRecords(this.store);
return;
});
test('cancel on create clears model (dlr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'single secret exists on list');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'jk');
await click(FORM.cancelBtn);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'psych');
await click(PAGE.breadcrumbAtIdx(1));
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
});
test('cancel on new version rolls back model (dlr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`);
assert.dom(PAGE.infoRowValue('foo')).exists('key has expected value');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version');
});
test('create & update root secret with default metadata (dlr)', async function (assert) {
const backend = this.backend;
const secretPath = 'some secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert
.dom(FORM.validationWarning)
.hasText(
"Path contains whitespace. If this is desired, you'll need to encode it with %20 in API requests."
);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
// Submit with API errors
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('foo')).hasText('***********');
await click(PAGE.infoRowToggleMasked('foo'));
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'secret value shows after toggle');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('You do not have access to secret metadata', 'shows no access state on metadata');
// Add new version
await click(PAGE.secretTab('Secret'));
assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version');
});
test('create nested secret with metadata (dlr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret
await typeIn(FORM.inputByAttr('path'), 'my/');
assert.dom(FORM.validation('path')).hasText("Path can't end in forward slash '/'.");
await typeIn(FORM.inputByAttr('path'), 'secret');
assert.dom(FORM.validation('path')).doesNotExist('form validation goes away');
await fillIn(FORM.keyInput(), 'password');
await fillIn(FORM.maskedValueInput(), 'kittens1234');
await click(FORM.toggleMetadata);
assert.dom(PAGE.create.metadataSection).exists('Shows metadata section after toggled');
// Check initial values
assert.dom(FORM.inputByAttr('maxVersions')).hasValue('0');
assert.dom(FORM.inputByAttr('casRequired')).isNotChecked();
assert.dom(FORM.toggleByLabel('Automate secret deletion')).isNotChecked();
// MaxVersions validation
await fillIn(FORM.inputByAttr('maxVersions'), 'seven');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('Maximum versions must be a number.');
await fillIn(FORM.inputByAttr('maxVersions'), '99999999999999999');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('You cannot go over 16 characters.');
await fillIn(FORM.inputByAttr('maxVersions'), '7');
// Fill in other metadata
await click(FORM.inputByAttr('casRequired'));
await click(FORM.toggleByLabel('Automate secret deletion'));
await fillIn(FORM.ttlValue('Automate secret deletion'), '1000');
// Fill in custom metadata
await fillIn(`${PAGE.create.metadataSection} ${FORM.keyInput()}`, 'team');
await fillIn(`${PAGE.create.metadataSection} ${FORM.valueInput()}`, 'UI');
// Fill in metadata
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('creates a secret at a sub-directory (dlr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list/app/`);
assert.dom(PAGE.list.item()).doesNotExist('Does not list any secrets');
await click(PAGE.list.createSecret);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/create?initialKey=app%2F`,
'Goes to create page with initialKey'
);
await typeIn(FORM.inputByAttr('path'), 'new');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('create new version of secret from older version (dlr)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=1`);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('version dropdown does not show');
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version');
});
});
module('metadata-maintainer persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(
tokenWithPolicyCmd(`data-list-reader-${this.backend}`, personas.metadataMaintainer(this.backend))
);
await authPage.login(token);
clearRecords(this.store);
return;
});
test('cancel on create clears model (mm)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'single secret exists on list');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'jk');
await click(FORM.cancelBtn);
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'psych');
await click(PAGE.breadcrumbAtIdx(1));
assert.dom(PAGE.list.item()).exists({ count: 1 }, 'same amount of secrets');
assert.dom(PAGE.list.item('app/')).hasText('app/', 'expected list item');
});
test('cancel on new version rolls back model (mm)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`);
assert.dom(PAGE.emptyStateTitle).hasText('You do not have permission to read this secret');
assert
.dom(PAGE.detail.createNewVersion)
.doesNotExist('create new version button now allowed since user cannot read existing');
});
test('create & update root secret with default metadata (mm)', async function (assert) {
const backend = this.backend;
const secretPath = 'some secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert
.dom(FORM.validationWarning)
.hasText(
"Path contains whitespace. If this is desired, you'll need to encode it with %20 in API requests."
);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
// Submit with API errors
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created tooltip does not show');
assert.dom(PAGE.infoRow).doesNotExist('secret data not shown');
assert.dom(PAGE.emptyStateTitle).hasText('You do not have permission to read this secret');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
assert
.dom(PAGE.infoRowValue('Delete version after'))
.hasText('Never delete', 'Delete version after has default 0s');
// Add new version
await click(PAGE.secretTab('Secret'));
assert.dom(PAGE.detail.createNewVersion).doesNotExist('create new version button not rendered');
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details/edit?version=1`);
assert
.dom(FORM.noReadAlert)
.hasText(
'Warning You do not have read permissions for this secret data. Saving will overwrite the existing secret.',
'shows alert for no read permissions'
);
assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
assert.dom(FORM.inputByAttr('path')).hasValue('app/first');
assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
assert.dom(FORM.keyInput()).hasValue('', 'first row has no key');
assert.dom(FORM.maskedValueInput()).hasValue('', 'first row has no value');
await fillIn(FORM.keyInput(), 'api_url');
await fillIn(FORM.maskedValueInput(), 'hashicorp.com');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('create nested secret with metadata (mm)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret
await typeIn(FORM.inputByAttr('path'), 'my/');
assert.dom(FORM.validation('path')).hasText("Path can't end in forward slash '/'.");
await typeIn(FORM.inputByAttr('path'), 'secret');
assert.dom(FORM.validation('path')).doesNotExist('form validation goes away');
await fillIn(FORM.keyInput(), 'password');
await fillIn(FORM.maskedValueInput(), 'kittens1234');
await click(FORM.toggleMetadata);
assert.dom(PAGE.create.metadataSection).exists('Shows metadata section after toggled');
// Check initial values
assert.dom(FORM.inputByAttr('maxVersions')).hasValue('0');
assert.dom(FORM.inputByAttr('casRequired')).isNotChecked();
assert.dom(FORM.toggleByLabel('Automate secret deletion')).isNotChecked();
// MaxVersions validation
await fillIn(FORM.inputByAttr('maxVersions'), 'seven');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('Maximum versions must be a number.');
await fillIn(FORM.inputByAttr('maxVersions'), '99999999999999999');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('You cannot go over 16 characters.');
await fillIn(FORM.inputByAttr('maxVersions'), '7');
// Fill in other metadata
await click(FORM.inputByAttr('casRequired'));
await click(FORM.toggleByLabel('Automate secret deletion'));
await fillIn(FORM.ttlValue('Automate secret deletion'), '1000');
// Fill in custom metadata
await fillIn(`${PAGE.create.metadataSection} ${FORM.keyInput()}`, 'team');
await fillIn(`${PAGE.create.metadataSection} ${FORM.valueInput()}`, 'UI');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('creates a secret at a sub-directory (mm)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list/app/`);
assert.dom(PAGE.list.item('first')).exists('Lists first sub-secret');
assert.dom(PAGE.list.item('new')).doesNotExist('Does not show new secret');
await click(PAGE.list.createSecret);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/create?initialKey=app%2F`,
'Goes to create page with initialKey'
);
await typeIn(FORM.inputByAttr('path'), 'new');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
test('create new version of secret from older version (mm)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionDropdown).hasText('Version 2');
await click(PAGE.detail.versionDropdown);
await click(`${PAGE.detail.version(1)} a`);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=1`,
'goes to version 1'
);
assert.dom(PAGE.detail.versionDropdown).hasText('Version 1');
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('version timestamp not shown');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('create new version button not rendered');
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details/edit?version=1`);
assert
.dom(FORM.noReadAlert)
.hasText(
'Warning You do not have read permissions for this secret data. Saving will overwrite the existing secret.',
'shows alert for no read permissions'
);
assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
assert.dom(FORM.inputByAttr('path')).hasValue('app/first');
assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
assert.dom(FORM.keyInput()).hasValue('', 'first row has no key');
assert.dom(FORM.maskedValueInput()).hasValue('', 'first row has no value');
await fillIn(FORM.keyInput(), 'api_url');
await fillIn(FORM.maskedValueInput(), 'hashicorp.com');
await click(FORM.saveBtn);
assert
.dom(FORM.messageError)
.hasText('Error 1 error occurred: * permission denied', 'API error shows on form');
});
});
module('secret-creator persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(
tokenWithPolicyCmd(`secret-creator-${this.backend}`, personas.secretCreator(this.backend))
);
await authPage.login(token);
clearRecords(this.store);
return;
});
test('cancel on create clears model (sc)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
assert.dom(PAGE.list.item()).doesNotExist('list view has no items');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'jk');
await click(FORM.cancelBtn);
assert.dom(PAGE.list.item()).doesNotExist('list view still has no items');
await click(PAGE.list.createSecret);
await fillIn(FORM.inputByAttr('path'), 'psych');
await click(PAGE.breadcrumbAtIdx(1));
assert.dom(PAGE.list.item()).doesNotExist('list view still has no items');
});
test('cancel on new version rolls back model (sc)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`);
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'no permissions state shows');
await click(PAGE.detail.createNewVersion);
await fillIn(FORM.keyInput(), 'bar');
await click(FORM.cancelBtn);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`,
'cancel goes to correct url'
);
assert.dom(PAGE.list.item()).doesNotExist('list view has no items');
await click(PAGE.detail.createNewVersion);
await fillIn(FORM.keyInput(), 'bar');
await click(PAGE.breadcrumbAtIdx(3));
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('app/first')}/details`,
'breadcrumb goes to correct url'
);
assert.dom(PAGE.list.item()).doesNotExist('list view has no items');
});
test('create & update root secret with default metadata (sc)', async function (assert) {
const backend = this.backend;
const secretPath = 'some secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert
.dom(FORM.validationWarning)
.hasText(
"Path contains whitespace. If this is desired, you'll need to encode it with %20 in API requests."
);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
// Submit with API errors
await click(FORM.saveBtn);
assert.dom(FORM.messageError).hasText('Error no data provided', 'API error shows on form');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
// Details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details`,
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created not shown');
assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText(
'You do not have access to read custom metadata',
'permissions empty state for custom metadata'
);
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('You do not have access to secret metadata', 'permissions empty state for secret metadata');
// Add new version
await click(PAGE.secretTab('Secret'));
await click(PAGE.detail.createNewVersion);
assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
assert.dom(FORM.inputByAttr('path')).hasValue(secretPath);
assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
assert.dom(FORM.keyInput()).hasValue('', 'row 1 is empty key');
assert.dom(FORM.maskedValueInput()).hasValue('', 'row 1 has empty value');
await fillIn(FORM.keyInput(), 'api_url');
await fillIn(FORM.maskedValueInput(), 'hashicorp.com');
await click(FORM.saveBtn);
// Back to details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=2`,
'goes back to details page'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created does not show');
assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
});
test('create nested secret with metadata (sc)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret
await typeIn(FORM.inputByAttr('path'), 'my/');
assert.dom(FORM.validation('path')).hasText("Path can't end in forward slash '/'.");
await typeIn(FORM.inputByAttr('path'), 'secret');
assert.dom(FORM.validation('path')).doesNotExist('form validation goes away');
await fillIn(FORM.keyInput(), 'password');
await fillIn(FORM.maskedValueInput(), 'kittens1234');
await click(FORM.toggleMetadata);
assert.dom(PAGE.create.metadataSection).exists('Shows metadata section after toggled');
// Check initial values
assert.dom(FORM.inputByAttr('maxVersions')).hasValue('0');
assert.dom(FORM.inputByAttr('casRequired')).isNotChecked();
assert.dom(FORM.toggleByLabel('Automate secret deletion')).isNotChecked();
// MaxVersions validation
await fillIn(FORM.inputByAttr('maxVersions'), 'seven');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('Maximum versions must be a number.');
await fillIn(FORM.inputByAttr('maxVersions'), '99999999999999999');
await click(FORM.saveBtn);
assert.dom(FORM.validation('maxVersions')).hasText('You cannot go over 16 characters.');
await fillIn(FORM.inputByAttr('maxVersions'), '7');
// Fill in other metadata
await click(FORM.inputByAttr('casRequired'));
await click(FORM.toggleByLabel('Automate secret deletion'));
await fillIn(FORM.ttlValue('Automate secret deletion'), '1000');
// Fill in custom metadata
await fillIn(`${PAGE.create.metadataSection} ${FORM.keyInput()}`, 'team');
await fillIn(`${PAGE.create.metadataSection} ${FORM.valueInput()}`, 'UI');
// Fill in metadata
await click(FORM.saveBtn);
// Details
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('my/secret')}/details`,
'goes back to details page'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('version created not shown');
assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
// Metadata
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText(
'You do not have access to read custom metadata',
'permissions empty state for custom metadata'
);
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('You do not have access to secret metadata', 'permissions empty state for secret metadata');
});
test('creates a secret at a sub-directory (sc)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/list/app/`);
assert.dom(PAGE.list.item()).doesNotExist('Does not list any secrets');
await click(PAGE.list.createSecret);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/create?initialKey=app%2F`,
'Goes to create page with initialKey'
);
await typeIn(FORM.inputByAttr('path'), 'new');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent('app/new')}/details`,
'Redirects to detail after save'
);
await click(PAGE.breadcrumbAtIdx(2));
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`, 'sub-dir page');
assert.dom(PAGE.list.item()).doesNotExist('Does not list any secrets');
});
test('create new version of secret from older version (sc)', async function (assert) {
const backend = this.backend;
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=1`);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('version dropdown does not show');
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created not shown');
await click(PAGE.detail.createNewVersion);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details/edit?version=1`,
'Goes to new version page'
);
assert
.dom(FORM.noReadAlert)
.hasText(
'Warning You do not have read permissions for this secret data. Saving will overwrite the existing secret.',
'shows alert for no read permissions'
);
assert.dom(FORM.keyInput()).hasValue('', 'Key input has empty value');
assert.dom(FORM.maskedValueInput()).hasValue('', 'Val input has empty value');
await fillIn(FORM.keyInput(), 'my-key');
await fillIn(FORM.maskedValueInput(), 'my-value');
await click(FORM.saveBtn);
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/app%2Ffirst/details?version=3`,
'redirects to details page'
);
assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
});
});
module('secret-nested-creator persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(
tokenWithPolicyCmd(
`secret-nested-creator-${this.backend}`,
personas.secretNestedCreator(this.backend)
)
);
await authPage.login(token);
clearRecords(this.store);
return;
});
test('can create a secret from the nested list view (snc)', async function (assert) {
assert.expect(1);
// go to nested secret directory list view
await visit(`/vault/secrets/${this.backend}/kv/list/app/`);
// correct popup menu items appear on list view
const popupSelector = `${PAGE.list.item('first')} ${PAGE.popup}`;
await click(popupSelector);
assert.dom(PAGE.list.listMenuCreate).exists('shows the option to create new version');
});
});
module('enterprise controlled access persona', function (hooks) {
hooks.beforeEach(async function () {
this.controlGroup = this.owner.lookup('service:control-group');
const userPolicy = `
path "${this.backend}/data/*" {
capabilities = ["create", "read", "update"]
control_group = {
max_ttl = "24h"
factor "authorizer" {
controlled_capabilities = ["create", "update"]
identity {
group_names = ["managers"]
approvals = 1
}
}
}
}
path "${this.backend}/metadata" {
capabilities = ["list", "read"]
}
path "${this.backend}/metadata/*" {
capabilities = ["list", "read"]
}
`;
const { userToken } = await setupControlGroup({
userPolicy,
backend: this.backend,
});
this.userToken = userToken;
await authPage.login(userToken);
clearRecords(this.store);
return;
});
test('create & update root secret with default metadata (cg)', async function (assert) {
const backend = this.backend;
// Known issue: control groups do not work correctly in UI when encodable characters in path
const secretPath = 'some-secret';
await visit(`/vault/secrets/${backend}/kv/list`);
await click(PAGE.list.createSecret);
// Create secret form -- validations
await click(FORM.saveBtn);
assert.dom(FORM.invalidFormAlert).hasText('There is an error with this form.');
assert.dom(FORM.validation('path')).hasText("Path can't be blank.");
await typeIn(FORM.inputByAttr('path'), secretPath);
assert.dom(PAGE.create.metadataSection).doesNotExist('Hides metadata section by default');
await fillIn(FORM.keyInput(), 'api_key');
await fillIn(FORM.maskedValueInput(), 'partyparty');
await click(FORM.saveBtn);
let tokenToUnwrap = this.controlGroup.tokenToUnwrap;
assert.deepEqual(
Object.keys(tokenToUnwrap),
['accessor', 'token', 'creation_path', 'creation_time', 'ttl'],
'stored tokenToUnwrap includes correct keys'
);
assert.strictEqual(
tokenToUnwrap.creation_path,
`${backend}/data/${secretPath}`,
'stored tokenToUnwrap includes correct creation path'
);
assert
.dom(FORM.messageError)
.includesText(
`Error A Control Group was encountered at ${backend}/data/${secretPath}.`,
'shows control group error'
);
await grantAccessForWrite({
token: tokenToUnwrap.token,
accessor: tokenToUnwrap.accessor,
creation_path: `${backend}/data/${secretPath}`,
originUrl: `/vault/secrets/${backend}/kv/create`,
userToken: this.userToken,
backend: this.backend,
});
// In a real scenario the user would stay on page, but in the test
// we fill in the same info and try again
await typeIn(FORM.inputByAttr('path'), secretPath);
await fillIn(FORM.keyInput(), 'this can be anything');
await fillIn(FORM.maskedValueInput(), 'this too, gonna use the wrapped data');
await click(FORM.saveBtn);
assert.strictEqual(this.controlGroup.tokenToUnwrap, null, 'clears tokenToUnwrap after successful save');
// Details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${secretPath}/details?version=1`,
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
// Metadata page
await click(PAGE.secretTab('Metadata'));
assert
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
.dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
assert
.dom(PAGE.infoRowValue('Delete version after'))
.hasText('Never delete', 'Delete version after has default 0s');
// Add new version
await click(PAGE.secretTab('Secret'));
await click(PAGE.detail.createNewVersion);
assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
assert.dom(FORM.inputByAttr('path')).hasValue(secretPath);
assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
assert.dom(FORM.keyInput()).hasValue('api_key');
assert.dom(FORM.maskedValueInput()).hasValue('partyparty');
await fillIn(FORM.keyInput(1), 'api_url');
await fillIn(FORM.maskedValueInput(1), 'hashicorp.com');
await click(FORM.saveBtn);
tokenToUnwrap = this.controlGroup.tokenToUnwrap;
assert.strictEqual(
tokenToUnwrap.creation_path,
`${backend}/data/${secretPath}`,
'stored tokenToUnwrap includes correct update path'
);
assert
.dom(FORM.messageError)
.includesText(
`Error A Control Group was encountered at ${backend}/data/${secretPath}.`,
'shows control group error'
);
// Normally the user stays on the page and tries again once approval is granted
// unmark on test so it doesn't use the control group on read at the same path
// when we return to the page after granting access below
this.controlGroup.unmarkTokenForUnwrap();
await grantAccessForWrite({
accessor: tokenToUnwrap.accessor,
token: tokenToUnwrap.token,
creation_path: `${backend}/data/${secretPath}`,
originUrl: `/vault/secrets/${backend}/kv/${secretPath}/details/edit`,
userToken: this.userToken,
backend: this.backend,
});
// Remark for unwrap as if we never left the page.
this.controlGroup.markTokenForUnwrap(tokenToUnwrap.accessor);
// No need to fill in data because we're using the stored wrapped request
// and the path already exists
await click(FORM.saveBtn);
assert.strictEqual(
this.controlGroup.tokenToUnwrap,
null,
'clears tokenToUnwrap after successful update'
);
// Back to details page
assert.strictEqual(
currentURL(),
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=2`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
assert.dom(PAGE.infoRow).exists({ count: 2 }, '2 rows of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
assert.dom(PAGE.infoRowValue('api_url')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
await click(PAGE.infoRowToggleMasked('api_url'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
assert.dom(PAGE.infoRowValue('api_url')).hasText('hashicorp.com', 'secret value shows after toggle');
});
});
});