diff --git a/ui/app/components/namespace-picker.js b/ui/app/components/namespace-picker.js index 517f16cb2b..8c2d14b99c 100644 --- a/ui/app/components/namespace-picker.js +++ b/ui/app/components/namespace-picker.js @@ -7,11 +7,10 @@ import { inject as service } from '@ember/service'; import { alias, gt } from '@ember/object/computed'; import Component from '@ember/component'; import { computed } from '@ember/object'; -import keyUtils from 'vault/lib/key-utils'; -import pathToTree from 'vault/lib/path-to-tree'; import { task, timeout } from 'ember-concurrency'; +import pathToTree from 'vault/lib/path-to-tree'; +import { ancestorKeysForKey } from 'core/utils/key-utils'; -const { ancestorKeysForKey } = keyUtils; const DOT_REPLACEMENT = '☃'; const ANIMATION_DURATION = 250; diff --git a/ui/app/controllers/vault/cluster/access/leases/list.js b/ui/app/controllers/vault/cluster/access/leases/list.js index 42ff83a518..89715ae1d9 100644 --- a/ui/app/controllers/vault/cluster/access/leases/list.js +++ b/ui/app/controllers/vault/cluster/access/leases/list.js @@ -6,8 +6,8 @@ import { inject as service } from '@ember/service'; import { computed } from '@ember/object'; import Controller, { inject as controller } from '@ember/controller'; -import utils from 'vault/lib/key-utils'; import ListController from 'core/mixins/list-controller'; +import { keyIsFolder } from 'core/utils/key-utils'; export default Controller.extend(ListController, { flashMessages: service(), @@ -26,7 +26,7 @@ export default Controller.extend(ListController, { isLoading: false, filterIsFolder: computed('filter', function () { - return !!utils.keyIsFolder(this.filter); + return !!keyIsFolder(this.filter); }), emptyTitle: computed('baseKey.id', 'filter', 'filterIsFolder', function () { diff --git a/ui/app/controllers/vault/cluster/secrets/backend/list.js b/ui/app/controllers/vault/cluster/secrets/backend/list.js index 8033b34ef2..e86c247051 100644 --- a/ui/app/controllers/vault/cluster/secrets/backend/list.js +++ b/ui/app/controllers/vault/cluster/secrets/backend/list.js @@ -7,10 +7,10 @@ import { or } from '@ember/object/computed'; import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; -import utils from 'vault/lib/key-utils'; import BackendCrumbMixin from 'vault/mixins/backend-crumb'; import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor'; import ListController from 'core/mixins/list-controller'; +import { keyIsFolder } from 'core/utils/key-utils'; export default Controller.extend(ListController, BackendCrumbMixin, WithNavToNearestAncestor, { flashMessages: service(), @@ -19,7 +19,7 @@ export default Controller.extend(ListController, BackendCrumbMixin, WithNavToNea tab: '', filterIsFolder: computed('filter', function () { - return !!utils.keyIsFolder(this.filter); + return !!keyIsFolder(this.filter); }), isConfigurableTab: or('isCertTab', 'isConfigure'), diff --git a/ui/app/mixins/key-mixin.js b/ui/app/mixins/key-mixin.js index f9d9aafb61..6b239c2b2c 100644 --- a/ui/app/mixins/key-mixin.js +++ b/ui/app/mixins/key-mixin.js @@ -5,7 +5,7 @@ import { computed } from '@ember/object'; import Mixin from '@ember/object/mixin'; -import utils from 'vault/lib/key-utils'; +import { keyIsFolder, keyPartsForKey, parentKeyForKey } from 'core/utils/key-utils'; export default Mixin.create({ // what attribute has the path for the key @@ -26,16 +26,16 @@ export default Mixin.create({ // rather than using defineProperty for all of these, // we're just going to hardcode the known keys for the path ('id' and 'path') isFolder: computed('id', 'path', function () { - return utils.keyIsFolder(this.pathVal()); + return keyIsFolder(this.pathVal()); }), keyParts: computed('id', 'path', function () { - return utils.keyPartsForKey(this.pathVal()); + return keyPartsForKey(this.pathVal()); }), parentKey: computed('id', 'path', 'isCreating', { get: function () { - return this.isCreating ? this.initialParentKey : utils.parentKeyForKey(this.pathVal()); + return this.isCreating ? this.initialParentKey : parentKeyForKey(this.pathVal()); }, set: function (_, value) { return value; diff --git a/ui/app/mixins/with-nav-to-nearest-ancestor.js b/ui/app/mixins/with-nav-to-nearest-ancestor.js index b7cc39a19d..dd0e31c3d1 100644 --- a/ui/app/mixins/with-nav-to-nearest-ancestor.js +++ b/ui/app/mixins/with-nav-to-nearest-ancestor.js @@ -4,8 +4,8 @@ */ import Mixin from '@ember/object/mixin'; -import utils from 'vault/lib/key-utils'; import { task } from 'ember-concurrency'; +import { ancestorKeysForKey } from 'core/utils/key-utils'; // This mixin is currently used in a controller and a component, but we // don't see cancellation of the task as the while loop runs in either @@ -18,7 +18,7 @@ import { task } from 'ember-concurrency'; // the ancestors array and transitions to the root export default Mixin.create({ navToNearestAncestor: task(function* (key) { - const ancestors = utils.ancestorKeysForKey(key); + const ancestors = ancestorKeysForKey(key); let errored = false; let nearest = ancestors.pop(); while (nearest) { diff --git a/ui/app/routes/vault/cluster/access/leases/show.js b/ui/app/routes/vault/cluster/access/leases/show.js index b8035e565b..3a2d530d17 100644 --- a/ui/app/routes/vault/cluster/access/leases/show.js +++ b/ui/app/routes/vault/cluster/access/leases/show.js @@ -3,20 +3,20 @@ * SPDX-License-Identifier: MPL-2.0 */ -import { set } from '@ember/object'; import { hash } from 'rsvp'; +import { set } from '@ember/object'; import Route from '@ember/routing/route'; -import UnloadModelRoute from 'vault/mixins/unload-model-route'; -import utils from 'vault/lib/key-utils'; import { inject as service } from '@ember/service'; +import { keyIsFolder, parentKeyForKey } from 'core/utils/key-utils'; +import UnloadModelRoute from 'vault/mixins/unload-model-route'; export default Route.extend(UnloadModelRoute, { store: service(), beforeModel() { const { lease_id: leaseId } = this.paramsFor(this.routeName); - const parentKey = utils.parentKeyForKey(leaseId); - if (utils.keyIsFolder(leaseId)) { + const parentKey = parentKeyForKey(leaseId); + if (keyIsFolder(leaseId)) { if (parentKey) { return this.transitionTo('vault.cluster.access.leases.list', parentKey); } else { diff --git a/ui/app/routes/vault/cluster/secrets/backend/actions.js b/ui/app/routes/vault/cluster/secrets/backend/actions.js index cc9e12f1bb..2bddf1ebc9 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/actions.js +++ b/ui/app/routes/vault/cluster/secrets/backend/actions.js @@ -3,8 +3,8 @@ * SPDX-License-Identifier: MPL-2.0 */ +import { parentKeyForKey } from 'core/utils/key-utils'; import EditBase from './secret-edit'; -import utils from 'vault/lib/key-utils'; export default EditBase.extend({ queryParams: { @@ -17,7 +17,7 @@ export default EditBase.extend({ beforeModel() { const { secret } = this.paramsFor(this.routeName); - const parentKey = utils.parentKeyForKey(secret); + const parentKey = parentKeyForKey(secret); const { backend } = this.paramsFor('vault.cluster.secrets.backend'); if (this.backendType(backend) !== 'transit') { if (parentKey) { diff --git a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js index 0ab7ec5caa..7e1be219f5 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -8,9 +8,9 @@ import { set } from '@ember/object'; import { resolve } from 'rsvp'; import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; -import utils from 'vault/lib/key-utils'; import UnloadModelRoute from 'vault/mixins/unload-model-route'; import { encodePath, normalizePath } from 'vault/utils/path-encoding-helpers'; +import { keyIsFolder, parentKeyForKey } from 'core/utils/key-utils'; export default Route.extend(UnloadModelRoute, { store: service(), @@ -79,9 +79,9 @@ export default Route.extend(UnloadModelRoute, { beforeModel({ to: { queryParams } }) { const secret = this.secretParam(); return this.buildModel(secret, queryParams).then(() => { - const parentKey = utils.parentKeyForKey(secret); + const parentKey = parentKeyForKey(secret); const mode = this.routeName.split('.').pop(); - if (mode === 'edit' && utils.keyIsFolder(secret)) { + if (mode === 'edit' && keyIsFolder(secret)) { if (parentKey) { return this.transitionTo('vault.cluster.secrets.backend.list', encodePath(parentKey)); } else { diff --git a/ui/app/routes/vault/cluster/secrets/backend/versions.js b/ui/app/routes/vault/cluster/secrets/backend/versions.js index dbe5ff25dd..d1283d7778 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/versions.js +++ b/ui/app/routes/vault/cluster/secrets/backend/versions.js @@ -4,10 +4,10 @@ */ import Route from '@ember/routing/route'; -import utils from 'vault/lib/key-utils'; import UnloadModelRoute from 'vault/mixins/unload-model-route'; import { normalizePath } from 'vault/utils/path-encoding-helpers'; import { inject as service } from '@ember/service'; +import { parentKeyForKey } from 'core/utils/key-utils'; export default Route.extend(UnloadModelRoute, { store: service(), @@ -16,7 +16,7 @@ export default Route.extend(UnloadModelRoute, { beforeModel() { const backendModel = this.modelFor('vault.cluster.secrets.backend'); const { secret } = this.paramsFor(this.routeName); - const parentKey = utils.parentKeyForKey(secret); + const parentKey = parentKeyForKey(secret); if (backendModel.get('isV2KV')) { return; } diff --git a/ui/lib/core/addon/components/key-value-header.js b/ui/lib/core/addon/components/key-value-header.js index 46a7748497..4c9e854d8b 100644 --- a/ui/lib/core/addon/components/key-value-header.js +++ b/ui/lib/core/addon/components/key-value-header.js @@ -4,7 +4,7 @@ */ import Component from '@glimmer/component'; -import utils from 'vault/lib/key-utils'; +import { ancestorKeysForKey, keyPartsForKey, keyWithoutParentKey } from 'core/utils/key-utils'; import { encodePath } from 'vault/utils/path-encoding-helpers'; /** @@ -60,8 +60,8 @@ export default class KeyValueHeader extends Component { const path = this.args.path; const currentPath = this.currentPath; const showCurrent = this.showCurrent; - const ancestors = utils.ancestorKeysForKey(baseKey); - const parts = utils.keyPartsForKey(baseKey); + const ancestors = ancestorKeysForKey(baseKey); + const parts = keyPartsForKey(baseKey); if (ancestors.length === 0) { crumbs.push({ label: baseKey, @@ -87,8 +87,8 @@ export default class KeyValueHeader extends Component { }); crumbs.push({ - label: utils.keyWithoutParentKey(baseKey), - text: this.stripTrailingSlash(utils.keyWithoutParentKey(baseKey)), + label: keyWithoutParentKey(baseKey), + text: this.stripTrailingSlash(keyWithoutParentKey(baseKey)), path: currentPath, model: baseKeyModel, }); diff --git a/ui/lib/core/addon/components/navigate-input.js b/ui/lib/core/addon/components/navigate-input.js index 955b34ed67..6c0b91e9e0 100644 --- a/ui/lib/core/addon/components/navigate-input.js +++ b/ui/lib/core/addon/components/navigate-input.js @@ -3,17 +3,17 @@ * SPDX-License-Identifier: MPL-2.0 */ +import Ember from 'ember'; import { debounce, later } from '@ember/runloop'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { guidFor } from '@ember/object/internals'; import Component from '@glimmer/component'; -// TODO MOVE THESE TO THE ADDON -import utils from 'vault/lib/key-utils'; -import keys from 'vault/lib/keycodes'; import { encodePath } from 'vault/utils/path-encoding-helpers'; -import Ember from 'ember'; +import { keyIsFolder, parentKeyForKey } from 'core/utils/key-utils'; +// TODO MOVE THESE TO THE ADDON +import keys from 'vault/lib/keycodes'; /** * @module NavigateInput @@ -94,7 +94,7 @@ export default class NavigateInput extends Component { if (mode.startsWith('secrets') && (!val || val === baseKey)) { return; } - if (this.args.filterMatchesKey && !utils.keyIsFolder(val)) { + if (this.args.filterMatchesKey && !keyIsFolder(val)) { const params = [routeFor('show', mode, this.args.urls), extraParams, this.keyForNav(val)].compact(); this.transitionToRoute(...params); } else { @@ -126,7 +126,7 @@ export default class NavigateInput extends Component { // pop to the nearest parentKey or to the root onEscape(val) { - const key = utils.parentKeyForKey(val) || ''; + const key = parentKeyForKey(val) || ''; this.args.filterDidChange(key); this.filterUpdated(key); } @@ -150,11 +150,11 @@ export default class NavigateInput extends Component { } // select the key to nav to, assumed to be a folder let key = val ? val.trim() : ''; - const isFolder = utils.keyIsFolder(key); + const isFolder = keyIsFolder(key); if (!isFolder) { // nav to the closest parentKey (or the root) - key = utils.parentKeyForKey(val) || ''; + key = parentKeyForKey(val) || ''; } const pageFilter = val.replace(key, ''); @@ -167,7 +167,7 @@ export default class NavigateInput extends Component { if (key) { args.push(key); } - if (pageFilter && !utils.keyIsFolder(pageFilter)) { + if (pageFilter && !keyIsFolder(pageFilter)) { args.push({ queryParams: { page: 1, diff --git a/ui/app/lib/key-utils.js b/ui/lib/core/addon/utils/key-utils.ts similarity index 52% rename from ui/app/lib/key-utils.js rename to ui/lib/core/addon/utils/key-utils.ts index e629037181..3dbdac9721 100644 --- a/ui/app/lib/key-utils.js +++ b/ui/lib/core/addon/utils/key-utils.ts @@ -3,37 +3,38 @@ * SPDX-License-Identifier: MPL-2.0 */ -function keyIsFolder(key) { +export function keyIsFolder(key: string) { return key ? !!key.match(/\/$/) : false; } -function keyPartsForKey(key) { +export function keyPartsForKey(key: string) { if (!key) { return null; } - var isFolder = keyIsFolder(key); - var parts = key.split('/'); + const isFolder = keyIsFolder(key); + const parts = key.split('/'); if (isFolder) { + // remove last item which is empty parts.pop(); } return parts.length > 1 ? parts : null; } -function parentKeyForKey(key) { - var parts = keyPartsForKey(key); +export function parentKeyForKey(key: string) { + const parts = keyPartsForKey(key); if (!parts) { - return null; + return ''; } return parts.slice(0, -1).join('/') + '/'; } -function keyWithoutParentKey(key) { +export function keyWithoutParentKey(key: string) { return key ? key.replace(parentKeyForKey(key), '') : null; } -function ancestorKeysForKey(key) { - var ancestors = [], - parentKey = parentKeyForKey(key); +export function ancestorKeysForKey(key: string) { + const ancestors = []; + let parentKey = parentKeyForKey(key); while (parentKey) { ancestors.unshift(parentKey); @@ -42,11 +43,3 @@ function ancestorKeysForKey(key) { return ancestors; } - -export default { - keyIsFolder, - keyPartsForKey, - parentKeyForKey, - keyWithoutParentKey, - ancestorKeysForKey, -}; diff --git a/ui/lib/core/app/utils/key-utils.js b/ui/lib/core/app/utils/key-utils.js new file mode 100644 index 0000000000..0cac938716 --- /dev/null +++ b/ui/lib/core/app/utils/key-utils.js @@ -0,0 +1,7 @@ +export { + keyIsFolder, + keyPartsForKey, + parentKeyForKey, + keyWithoutParentKey, + ancestorKeysForKey, +} from 'core/utils/key-utils'; diff --git a/ui/tests/unit/utils/key-utils-test.js b/ui/tests/unit/utils/key-utils-test.js new file mode 100644 index 0000000000..ea7331b41d --- /dev/null +++ b/ui/tests/unit/utils/key-utils-test.js @@ -0,0 +1,62 @@ +import { + ancestorKeysForKey, + keyIsFolder, + keyPartsForKey, + keyWithoutParentKey, + parentKeyForKey, +} from 'vault/utils/key-utils'; +import { module, test } from 'qunit'; + +module('Unit | Utility | key-utils', function () { + test('keyIsFolder', function (assert) { + let result = keyIsFolder('foo'); + assert.false(result, 'not folder'); + + result = keyIsFolder('foo/'); + assert.true(result, 'is folder'); + + result = keyIsFolder('foo/bar'); + assert.false(result, 'not folder'); + }); + test('keyPartsForKey', function (assert) { + let result = keyPartsForKey(''); + assert.strictEqual(result, null, 'falsy value returns null'); + + result = keyPartsForKey('foo'); + assert.strictEqual(result, null, 'returns null if not a folder'); + + result = keyPartsForKey('foo/bar'); + assert.deepEqual(result, ['foo', 'bar'], 'returns parts of key'); + + result = keyPartsForKey('foo/bar/'); + assert.deepEqual(result, ['foo', 'bar'], 'returns parts of key when ends in slash'); + }); + test('parentKeyForKey', function (assert) { + let result = parentKeyForKey('my/very/nested/secret/path'); + assert.strictEqual(result, 'my/very/nested/secret/', 'returns parent path for key'); + + result = parentKeyForKey('my/nested/secret/'); + assert.strictEqual(result, 'my/nested/', 'returns correct parents'); + + result = parentKeyForKey('my-secret'); + assert.strictEqual(result, '', 'returns empty string when no parents'); + }); + test('keyWithoutParentKey', function (assert) { + let result = keyWithoutParentKey('my/very/nested/secret/path'); + assert.strictEqual(result, 'path', 'returns key without parent key'); + + result = keyWithoutParentKey('my-secret'); + assert.strictEqual(result, 'my-secret', 'returns path when no parent'); + + result = keyWithoutParentKey('folder/'); + assert.strictEqual(result, 'folder/', 'returns path as-is when folder without parent'); + }); + test('ancestorKeysForKey', function (assert) { + const expected = ['my/', 'my/very/', 'my/very/nested/', 'my/very/nested/secret/']; + let result = ancestorKeysForKey('my/very/nested/secret/path'); + assert.deepEqual(result, expected, 'returns array of ancestor paths'); + + result = ancestorKeysForKey('foobar'); + assert.deepEqual(result, [], 'returns empty array for root path'); + }); +});