diff --git a/changelog/24404.txt b/changelog/24404.txt
new file mode 100644
index 0000000000..6fab70d0bf
--- /dev/null
+++ b/changelog/24404.txt
@@ -0,0 +1,3 @@
+```release-note:bug
+ui: fix issue where kv v2 capabilities checks were not passing in the full secret path if secret was inside a directory.
+```
diff --git a/ui/app/models/kv/metadata.js b/ui/app/models/kv/metadata.js
index 1f998f488a..43dd20fc91 100644
--- a/ui/app/models/kv/metadata.js
+++ b/ui/app/models/kv/metadata.js
@@ -95,9 +95,14 @@ export default class KvSecretMetadataModel extends Model {
};
}
+ get permissionsPath() {
+ return this.fullSecretPath || this.path;
+ }
+
// permissions needed for the list view where kv/data has not yet been called. Allows us to conditionally show action items in the LinkedBlock popups.
- @lazyCapabilities(apiPath`${'backend'}/data/${'path'}`, 'backend', 'path') dataPath;
- @lazyCapabilities(apiPath`${'backend'}/metadata/${'path'}`, 'backend', 'path') metadataPath;
+ @lazyCapabilities(apiPath`${'backend'}/data/${'permissionsPath'}`, 'backend', 'permissionsPath') dataPath;
+ @lazyCapabilities(apiPath`${'backend'}/metadata/${'permissionsPath'}`, 'backend', 'permissionsPath')
+ metadataPath;
get canDeleteMetadata() {
return this.metadataPath.get('canDelete') !== false;
diff --git a/ui/lib/kv/addon/components/page/list.hbs b/ui/lib/kv/addon/components/page/list.hbs
index a7b5bb5d41..0accd8de08 100644
--- a/ui/lib/kv/addon/components/page/list.hbs
+++ b/ui/lib/kv/addon/components/page/list.hbs
@@ -95,7 +95,11 @@
{{/if}}
{{#if metadata.canCreateVersionData}}
-
+
Create new version
@@ -106,6 +110,7 @@
@isInDropdown={{true}}
@onConfirmAction={{fn this.onDelete metadata}}
@confirmMessage="This will permanently delete this secret and all its versions."
+ data-test-popup-metadata-delete
/>
{{/if}}
{{/if}}
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
index b6fb939e0e..49567b1f50 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
@@ -1003,6 +1003,26 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
});
});
+ module('secret-nested-creator persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(
+ tokenWithPolicyCmd('secret-nested-creator', 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');
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
index db3a80f281..c96e70f061 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
@@ -40,9 +40,11 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
this.store = this.owner.lookup('service:store');
this.backend = `kv-delete-${uuidv4()}`;
this.secretPath = 'bad-secret';
+ this.nestedSecretPath = 'app/nested/bad-secret';
await authPage.login();
await runCmd(mountEngineCmd('kv-v2', this.backend), false);
await writeVersionedSecret(this.backend, this.secretPath, 'foo', 'bar', 4);
+ await writeVersionedSecret(this.backend, this.nestedSecretPath, 'foo', 'bar', 1);
await writeVersionedSecret(this.backend, 'nuke', 'foo', 'bar', 2);
// Delete latest version for testing undelete for users that can't delete
await runCmd(deleteLatestCmd(this.backend, 'nuke'));
@@ -353,6 +355,33 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
});
});
+ module('secret-nested-creator persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(
+ tokenWithPolicyCmd('secret-nested-creator', personas.secretNestedCreator(this.backend))
+ );
+ await authPage.login(token);
+ clearRecords(this.store);
+ return;
+ });
+ test('can delete all secret versions 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/nested`);
+ // correct popup menu items appear on list view
+ const popupSelector = `${PAGE.list.item('bad-secret')} ${PAGE.popup}`;
+ await click(popupSelector);
+ assert.dom(PAGE.list.listMenuDelete).exists('shows the option to permanently delete');
+ });
+ test('can not delete all secret versions from root list view (snc)', async function (assert) {
+ assert.expect(1);
+ // go to root secret directory list view
+ await visit(`/vault/secrets/${this.backend}/kv/list`);
+ // shows overview card and not list view
+ assert.dom(PAGE.list.overviewCard).exists('renders overview card');
+ });
+ });
+
module('secret-creator persona', function (hooks) {
hooks.beforeEach(async function () {
const token = await runCmd(tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend)));
diff --git a/ui/tests/helpers/kv/kv-selectors.js b/ui/tests/helpers/kv/kv-selectors.js
index 742d6217fa..82adf0df84 100644
--- a/ui/tests/helpers/kv/kv-selectors.js
+++ b/ui/tests/helpers/kv/kv-selectors.js
@@ -58,6 +58,8 @@ export const PAGE = {
createSecret: '[data-test-toolbar-create-secret]',
item: (secret) => (!secret ? '[data-test-list-item]' : `[data-test-list-item="${secret}"]`),
filter: `[data-test-kv-list-filter]`,
+ listMenuDelete: `[data-test-popup-metadata-delete]`,
+ listMenuCreate: `[data-test-popup-create-new-version]`,
overviewCard: '[data-test-overview-card-container="View secret"]',
overviewInput: '[data-test-view-secret] input',
overviewButton: '[data-test-get-secret-detail]',
diff --git a/ui/tests/helpers/policy-generator/kv.js b/ui/tests/helpers/policy-generator/kv.js
index 7794e0493d..bcd91ed0ee 100644
--- a/ui/tests/helpers/policy-generator/kv.js
+++ b/ui/tests/helpers/policy-generator/kv.js
@@ -25,6 +25,14 @@ export const dataPolicy = ({ backend, secretPath = '*', capabilities = root }) =
`;
};
+export const dataNestedPolicy = ({ backend, secretPath = '*', capabilities = root }) => {
+ return `
+ path "${backend}/data/app/${secretPath}" {
+ capabilities = [${format(capabilities)}]
+ }
+ `;
+};
+
export const metadataPolicy = ({ backend, secretPath = '*', capabilities = root }) => {
// "delete" capability on this path can destroy all versions
return `
@@ -34,6 +42,14 @@ export const metadataPolicy = ({ backend, secretPath = '*', capabilities = root
`;
};
+export const metadataNestedPolicy = ({ backend, secretPath = '*', capabilities = root }) => {
+ return `
+ path "${backend}/metadata/app/${secretPath}" {
+ capabilities = [${format(capabilities)}]
+ }
+ `;
+};
+
export const metadataListPolicy = (backend) => {
return `
path "${backend}/metadata" {
@@ -71,11 +87,13 @@ export const personas = {
dataListReader: (backend) =>
dataPolicy({ backend, capabilities: ['read', 'delete'] }) + metadataListPolicy(backend),
metadataMaintainer: (backend) =>
- metadataListPolicy(backend) +
metadataPolicy({ backend, capabilities: ['create', 'read', 'update', 'list'] }) +
deleteVersionsPolicy({ backend }) +
undeleteVersionsPolicy({ backend }) +
destroyVersionsPolicy({ backend }),
+ secretNestedCreator: (backend) =>
+ dataNestedPolicy({ backend, capabilities: ['create', 'update'] }) +
+ metadataNestedPolicy({ backend, capabilities: ['list', 'delete'] }),
secretCreator: (backend) =>
dataPolicy({ backend, capabilities: ['create', 'update'] }) +
metadataPolicy({ backend, capabilities: ['delete'] }),