mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
Bug Fix UI: Await capabilities on KVv1 secret edit route to properly access methods on the promise proxy (#10548) (#10661)
* the fix await the promose proxy and assign it to const to access * add changelog * add test coverage * return comment * remove uncessary await now that we await higher up Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
This commit is contained in:
parent
47a7482e42
commit
0728f0a0ae
5 changed files with 52 additions and 13 deletions
3
changelog/_10548.txt
Normal file
3
changelog/_10548.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: Resolved a regression that prevented users with create and update permissions on KV v1 secrets from opening the edit view. The UI now correctly recognizes these capabilities and allows editing without requiring full read access.
|
||||
```
|
||||
|
|
@ -133,16 +133,10 @@
|
|||
<MessageError @model={{@modelForData}} @errorMessage={{this.error}} />
|
||||
<NamespaceReminder @mode="edit" @noun="secret" />
|
||||
{{#if (eq @canReadSecret false)}}
|
||||
<Hds::Alert @type="inline" @color="warning" class="has-bottom-margin-s" as |A|>
|
||||
<Hds::Alert @type="inline" @color="warning" class="has-bottom-margin-s" data-test-secret-no-read-permissions as |A|>
|
||||
<A.Title>Warning</A.Title>
|
||||
<A.Description>
|
||||
<ul class={{if (eq @canReadSecret false) "bullet"}}>
|
||||
{{#if (eq @canReadSecret false)}}
|
||||
<li data-test-warning-no-read-permissions>
|
||||
You do not have read permissions. If a secret exists at this path creating a new secret will overwrite it.
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
You do not have read permissions. If a secret exists at this path creating a new secret will overwrite it.
|
||||
</A.Description>
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
{{#if (and (eq @mode "show") @canUpdateSecret)}}
|
||||
{{#let (concat "vault.cluster.secrets.backend." (if (eq @mode "show") "edit" "show")) as |targetRoute|}}
|
||||
<ToolbarLink @route={{targetRoute}} @model={{@model.id}} @replace={{true}} data-test-secret-edit="true">
|
||||
<ToolbarLink @route={{targetRoute}} @model={{@model.id}} @replace={{true}} data-test-secret-edit>
|
||||
Edit secret
|
||||
</ToolbarLink>
|
||||
{{/let}}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,10 @@ export default Route.extend({
|
|||
return types[engineType];
|
||||
},
|
||||
|
||||
handleSecretModelError(capabilities, secretId, modelType, error) {
|
||||
async handleSecretModelError(capabilitiesPromise, secretId, modelType, error) {
|
||||
// capabilities is a promise proxy, not a real object
|
||||
// to work around this we explicitly assign it to a const and await it
|
||||
const capabilities = await capabilitiesPromise;
|
||||
// can't read the path and don't have update capability, so re-throw
|
||||
if (!capabilities.canUpdate && modelType === 'secret') {
|
||||
throw error;
|
||||
|
|
@ -186,8 +189,7 @@ export default Route.extend({
|
|||
// we've failed the read request, but if it's a kv-v1 type backend, we want to
|
||||
// do additional checks of the capabilities
|
||||
if (err.httpStatus === 403 && modelType === 'secret') {
|
||||
await capabilities;
|
||||
secretModel = this.handleSecretModelError(capabilities, secret, modelType, err);
|
||||
secretModel = await this.handleSecretModelError(capabilities, secret, modelType, err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import listPage from 'vault/tests/pages/secrets/backend/list';
|
|||
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { writeSecret, writeVersionedSecret } from 'vault/tests/helpers/kv/kv-run-commands';
|
||||
import { runCmd } from 'vault/tests/helpers/commands';
|
||||
import { runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
|
||||
import { PAGE } from 'vault/tests/helpers/kv/kv-selectors';
|
||||
import codemirror, { setCodeEditorValue } from 'vault/tests/helpers/codemirror';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
|
@ -94,6 +94,7 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
hooks.afterEach(async function () {
|
||||
await runCmd([`delete sys/mounts/${this.backend}`]);
|
||||
});
|
||||
|
||||
test('it can create a secret when check-and-set is required', async function (assert) {
|
||||
const secretPath = 'foo/bar';
|
||||
const output = await runCmd(`write ${this.backend}/config cas_required=true`);
|
||||
|
|
@ -110,6 +111,7 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
'redirects to the overview page'
|
||||
);
|
||||
});
|
||||
|
||||
test('it navigates to version history and to a specific version', async function (assert) {
|
||||
assert.expect(4);
|
||||
const secretPath = `specific-version`;
|
||||
|
|
@ -143,9 +145,11 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
await mountSecrets.version(1);
|
||||
await click(GENERAL.submitButton);
|
||||
});
|
||||
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd([`delete sys/mounts/${this.backend}`]);
|
||||
});
|
||||
|
||||
test('version 1 performs the correct capabilities lookup', async function (assert) {
|
||||
// TODO: while this should pass it doesn't really do anything anymore for us as v1 and v2 are completely separate.
|
||||
const secretPath = 'foo/bar';
|
||||
|
|
@ -158,6 +162,41 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
);
|
||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||
});
|
||||
|
||||
test('version 1 token without read permissions can create and update a secret', async function (assert) {
|
||||
const updatePersonaToken = await runCmd(
|
||||
tokenWithPolicyCmd(
|
||||
'read-all',
|
||||
`
|
||||
path "${this.backend}/*" {
|
||||
capabilities = ["create", "update", "list"]
|
||||
}
|
||||
# used to delete the engine after test done in afterEach hook
|
||||
path "sys/mounts/${this.backend}" {
|
||||
capabilities = ["delete"]
|
||||
}
|
||||
`
|
||||
)
|
||||
);
|
||||
|
||||
await login(updatePersonaToken);
|
||||
await visit(`/vault/secrets-engines/${this.backend}/list`);
|
||||
await click(SS.createSecretLink);
|
||||
await createSecret('test', 'foo', 'bar');
|
||||
await click('[data-test-secret-edit]', 'can click edit button');
|
||||
// edit only without read permissions
|
||||
assert
|
||||
.dom('[data-test-secret-no-read-permissions] .hds-alert__description')
|
||||
.hasText(
|
||||
'You do not have read permissions. If a secret exists at this path creating a new secret will overwrite it.',
|
||||
'Displays warning about no read permissions'
|
||||
);
|
||||
await fillIn('[data-test-secret-key]', 'new');
|
||||
await fillIn('[data-test-secret-value] textarea', 'new');
|
||||
await click(GENERAL.submitButton);
|
||||
assert.dom(GENERAL.latestFlashContent).includesText('Secret test updated successfully.');
|
||||
});
|
||||
|
||||
// https://github.com/hashicorp/vault/issues/5960
|
||||
test('version 1: nested paths creation maintains ability to navigate the tree', async function (assert) {
|
||||
const enginePath = this.backend;
|
||||
|
|
@ -224,6 +263,7 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
|
|||
'redirected to the list page on delete'
|
||||
);
|
||||
});
|
||||
|
||||
test('paths are properly encoded', async function (assert) {
|
||||
const backend = this.backend;
|
||||
const paths = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue