Skip to content

Commit

Permalink
Add directory paths to KV capabilities checks (#24404)
Browse files Browse the repository at this point in the history
* add getter to metadata model

* add changelog and data model fix

* add test coverage

* add nested create coverage

* Update 24404.txt

* remove from data model

* return to how it was
  • Loading branch information
Monkeychip committed Dec 7, 2023
1 parent 22e1781 commit e1f585c
Show file tree
Hide file tree
Showing 7 changed files with 2,006 additions and 1,913 deletions.
3 changes: 3 additions & 0 deletions changelog/24404.txt
Original file line number Diff line number Diff line change
@@ -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.
```
226 changes: 120 additions & 106 deletions ui/app/models/kv/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,112 +3,126 @@
* SPDX-License-Identifier: MPL-2.0
*/

import Model, { attr } from '@ember-data/model';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { withModelValidations } from 'vault/decorators/model-validations';
import { withFormFields } from 'vault/decorators/model-form-fields';
import { keyIsFolder } from 'core/utils/key-utils';
import { isDeleted } from 'kv/utils/kv-deleted';

const validations = {
maxVersions: [
{ type: 'number', message: 'Maximum versions must be a number.' },
{ type: 'length', options: { min: 1, max: 16 }, message: 'You cannot go over 16 characters.' },
],
export const PAGE = {
// General selectors that are common between pages
title: '[data-test-header-title]',
breadcrumbs: '[data-test-breadcrumbs]',
breadcrumb: '[data-test-breadcrumbs] li',
breadcrumbAtIdx: (idx) => `[data-test-crumb="${idx}"] a`,
infoRow: '[data-test-component="info-table-row"]',
infoRowValue: (label) => `[data-test-value-div="${label}"]`,
infoRowToggleMasked: (label) => `[data-test-value-div="${label}"] [data-test-button="toggle-masked"]`,
secretTab: (tab) => (tab ? `[data-test-secrets-tab="${tab}"]` : '[data-test-secrets-tab]'),
emptyStateTitle: '[data-test-empty-state-title]',
emptyStateMessage: '[data-test-empty-state-message]',
emptyStateActions: '[data-test-empty-state-actions]',
popup: '[data-test-popup-menu-trigger]',
error: {
title: '[data-test-page-error] h1',
message: '[data-test-page-error] p',
},
toolbar: 'nav.toolbar',
toolbarAction: 'nav.toolbar-actions .toolbar-link',
secretRow: '[data-test-component="info-table-row"]', // replace with infoRow
// specific page selectors
backends: {
link: (backend) => `[data-test-secrets-backend-link="${backend}"]`,
},
metadata: {
editBtn: '[data-test-edit-metadata]',
addCustomMetadataBtn: '[data-test-add-custom-metadata]',
customMetadataSection: '[data-test-kv-custom-metadata-section]',
secretMetadataSection: '[data-test-kv-metadata-section]',
deleteMetadata: '[data-test-kv-delete="delete-metadata"]',
},
detail: {
versionTimestamp: '[data-test-kv-version-tooltip-trigger]',
versionDropdown: '[data-test-version-dropdown]',
version: (number) => `[data-test-version="${number}"]`,
createNewVersion: '[data-test-create-new-version]',
delete: '[data-test-kv-delete="delete"]',
destroy: '[data-test-kv-delete="destroy"]',
undelete: '[data-test-kv-delete="undelete"]',
copy: '[data-test-copy-menu-trigger]',
deleteModal: '[data-test-delete-modal]',
deleteModalTitle: '[data-test-delete-modal] [data-test-modal-title]',
deleteOption: 'input#delete-version',
deleteOptionLatest: 'input#delete-latest-version',
deleteConfirm: '[data-test-delete-modal-confirm]',
},
edit: {
toggleDiff: '[data-test-toggle-input="Show diff"',
toggleDiffDescription: '[data-test-diff-description]',
},
list: {
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]',
pagination: '[data-test-pagination]',
paginationInfo: '.hds-pagination-info',
paginationNext: '.hds-pagination-nav__arrow--direction-next',
paginationSelected: '.hds-pagination-nav__number--is-selected',
},
versions: {
icon: (version) => `[data-test-icon-holder="${version}"]`,
linkedBlock: (version) =>
version ? `[data-test-version-linked-block="${version}"]` : '[data-test-version-linked-block]',
versionMenu: (version) => `[data-test-version-linked-block="${version}"] [data-test-popup-menu-trigger]`,
createFromVersion: (version) => `[data-test-create-new-version-from="${version}"]`,
},
diff: {
visualDiff: '[data-test-visual-diff]',
added: `.jsondiffpatch-added`,
deleted: `.jsondiffpatch-deleted`,
},
create: {
metadataSection: '[data-test-metadata-section]',
},
paths: {
copyButton: (label) => `${PAGE.infoRowValue(label)} button`,
codeSnippet: (section) => `[data-test-code-snippet][data-test-commands="${section}"] code`,
snippetCopy: (section) => `[data-test-code-snippet][data-test-commands="${section}"] button`,
},
};
const formFieldProps = ['customMetadata', 'maxVersions', 'casRequired', 'deleteVersionAfter'];

@withModelValidations(validations)
@withFormFields(formFieldProps)
export default class KvSecretMetadataModel extends Model {
@attr('string') backend;
@attr('string') path;
@attr('string') fullSecretPath;

@attr('number', {
defaultValue: 0,
label: 'Maximum number of versions',
subText:
'The number of versions to keep per key. Once the number of keys exceeds the maximum number set here, the oldest version will be permanently deleted.',
})
maxVersions;

@attr('boolean', {
defaultValue: false,
label: 'Require Check and Set',
subText: `Writes will only be allowed if the key's current version matches the version specified in the cas parameter.`,
})
casRequired;

@attr('string', {
defaultValue: '0s',
editType: 'ttl',
label: 'Automate secret deletion',
helperTextDisabled: `A secret's version must be manually deleted.`,
helperTextEnabled: 'Delete all new versions of this secret after:',
})
deleteVersionAfter;

@attr('object', {
editType: 'kv',
subText: 'An optional set of informational key-value pairs that will be stored with all secret versions.',
})
customMetadata;

// Additional Params only returned on the GET response.
@attr('string') createdTime;
@attr('number') currentVersion;
@attr('number') oldestVersion;
@attr('string') updatedTime;
@attr('object') versions;

// used for KV list and list-directory view
get pathIsDirectory() {
// ex: beep/
return keyIsFolder(this.path);
}

// cannot use isDeleted due to ember property conflict
get isSecretDeleted() {
return isDeleted(this.deletionTime);
}

// turns version object into an array for version dropdown menu
get sortedVersions() {
const array = [];
for (const key in this.versions) {
this.versions[key].isSecretDeleted = isDeleted(this.versions[key].deletion_time);
array.push({ version: key, ...this.versions[key] });
}
// version keys are in order created with 1 being the oldest, we want newest first
return array.reverse();
}

// helps in long logic statements for state of a currentVersion
get currentSecret() {
if (!this.versions || !this.currentVersion) return false;
const data = this.versions[this.currentVersion];
const state = data.destroyed ? 'destroyed' : isDeleted(data.deletion_time) ? 'deleted' : 'created';
return {
state,
isDeactivated: state !== 'created',
};
}

// 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;
// Form/Interactive selectors that are common between pages and forms
export const FORM = {
inputByAttr: (attr) => `[data-test-input="${attr}"]`,
fieldByAttr: (attr) => `[data=test=field="${attr}"]`, // formfield
toggleJson: '[data-test-toggle-input="json"]',
toggleMasked: '[data-test-button="toggle-masked"]',
toggleMetadata: '[data-test-metadata-toggle]',
jsonEditor: '[data-test-component="code-mirror-modifier"]',
ttlValue: (name) => `[data-test-ttl-value="${name}"]`,
toggleByLabel: (label) => `[data-test-ttl-toggle="${label}"]`,
dataInputLabel: ({ isJson = false }) =>
isJson ? '[data-test-component="json-editor-title"]' : '[data-test-kv-label]',
// <KvObjectEditor>
kvLabel: '[data-test-kv-label]',
kvRow: '[data-test-kv-row]',
keyInput: (idx = 0) => `[data-test-kv-key="${idx}"]`,
valueInput: (idx = 0) => `[data-test-kv-value="${idx}"]`,
maskedValueInput: (idx = 0) => `[data-test-kv-value="${idx}"] [data-test-textarea]`,
addRow: (idx = 0) => `[data-test-kv-add-row="${idx}"]`,
deleteRow: (idx = 0) => `[data-test-kv-delete-row="${idx}"]`,
// Alerts & validation
inlineAlert: '[data-test-inline-alert]',
validation: (attr) => `[data-test-field="${attr}"] [data-test-inline-alert]`,
messageError: '[data-test-message-error]',
validationWarning: '[data-test-validation-warning]',
invalidFormAlert: '[data-test-invalid-form-alert]',
versionAlert: '[data-test-secret-version-alert]',
noReadAlert: '[data-test-secret-no-read-alert]',
// Form btns
saveBtn: '[data-test-kv-save]',
cancelBtn: '[data-test-kv-cancel]',
};

get canDeleteMetadata() {
return this.metadataPath.get('canDelete') !== false;
}
get canReadMetadata() {
return this.metadataPath.get('canRead') !== false;
}
get canUpdateMetadata() {
return this.metadataPath.get('canUpdate') !== false;
}
get canCreateVersionData() {
return this.dataPath.get('canUpdate') !== false;
}
}
export const parseJsonEditor = (find) => {
return JSON.parse(find(FORM.jsonEditor).innerText);
};
Loading

0 comments on commit e1f585c

Please sign in to comment.