diff --git a/ui/app/adapters/kv/config.js b/ui/app/adapters/kv/config.js
deleted file mode 100644
index ea3b8699aa..0000000000
--- a/ui/app/adapters/kv/config.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import ApplicationAdapter from '../application';
-export default class KvConfigAdapter extends ApplicationAdapter {
- namespace = 'v1';
-
- urlForFindRecord(id) {
- return `${this.buildURL()}/${id}/config`;
- }
-}
diff --git a/ui/app/adapters/kv/data.js b/ui/app/adapters/kv/data.js
deleted file mode 100644
index 9ed2039a52..0000000000
--- a/ui/app/adapters/kv/data.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import ApplicationAdapter from '../application';
-import { kvDataPath, kvDeletePath, kvDestroyPath, kvSubkeysPath, kvUndeletePath } from 'vault/utils/kv-path';
-import { assert } from '@ember/debug';
-import ControlGroupError from 'vault/lib/control-group-error';
-
-export default class KvDataAdapter extends ApplicationAdapter {
- namespace = 'v1';
-
- _url(fullPath) {
- return `${this.buildURL()}/${fullPath}`;
- }
-
- createRecord(store, type, snapshot) {
- const { backend, path } = snapshot.record;
- const url = this._url(kvDataPath(backend, path));
-
- return this.ajax(url, 'POST', { data: this.serialize(snapshot) }).then((res) => {
- return {
- data: {
- id: kvDataPath(backend, path, res.data.version),
- backend,
- path,
- ...res.data,
- },
- };
- });
- }
-
- fetchSubkeys(backend, path, query) {
- const url = this._url(kvSubkeysPath(backend, path, query));
- return (
- this.ajax(url, 'GET')
- .then((resp) => resp.data)
- // deleted/destroyed secret versions throw an error
- // but still have metadata that we want to return
- .catch((errorOrResponse) => {
- return this.parseErrorOrResponse(errorOrResponse, { backend, path }, true);
- })
- );
- }
-
- fetchWrapInfo(query) {
- const { backend, path, version, wrapTTL } = query;
- const id = kvDataPath(backend, path, version);
- return this.ajax(this._url(id), 'GET', { wrapTTL }).then((resp) => resp.wrap_info);
- }
-
- // patching a secret happens without retrieving the ember data model
- // so we use a custom method instead of updateRecord
- patchSecret(backend, path, patchData, version) {
- const url = this._url(kvDataPath(backend, path));
- const data = {
- options: { cas: version },
- data: patchData,
- };
- return this.ajax(url, 'PATCH', { data });
- }
-
- queryRecord(store, type, query) {
- const { backend, path, version } = query;
- // ID is the full path for the data (including version)
- let id = kvDataPath(backend, path, version);
- return this.ajax(this._url(id), 'GET')
- .then((resp) => {
- // if no version is queried, add version from response to ID
- // otherwise duplicate ember data models will exist in store
- // (one with an ID that includes the version and one without)
- if (!version) {
- id = kvDataPath(backend, path, resp.data.metadata.version);
- }
- return {
- ...resp,
- data: {
- id,
- backend,
- path,
- ...resp.data,
- },
- };
- })
- .catch((errorOrResponse) => {
- return this.parseErrorOrResponse(errorOrResponse, { id, backend, path, version });
- });
- }
-
- /* Five types of delete operations */
- deleteRecord(store, type, snapshot) {
- const { backend, path } = snapshot.record;
- const { deleteType, deleteVersions } = snapshot.adapterOptions;
-
- if (!backend || !path) {
- throw new Error('The request to delete or undelete is missing required attributes.');
- }
-
- switch (deleteType) {
- case 'delete-latest-version':
- return this.ajax(this._url(kvDataPath(backend, path)), 'DELETE');
- case 'delete-version':
- return this.ajax(this._url(kvDeletePath(backend, path)), 'POST', {
- data: { versions: deleteVersions },
- });
- case 'destroy':
- return this.ajax(this._url(kvDestroyPath(backend, path)), 'PUT', {
- data: { versions: deleteVersions },
- });
- case 'undelete':
- return this.ajax(this._url(kvUndeletePath(backend, path)), 'POST', {
- data: { versions: deleteVersions },
- });
- default:
- assert('deleteType must be one of delete-latest-version, delete-version, destroy, or undelete.');
- }
- }
-
- handleResponse(status, headers, payload, requestData) {
- // after deleting a secret version, data is null and the API returns a 404
- // but there could be relevant metadata
- if (status === 404 && payload.data?.metadata) {
- return super.handleResponse(200, headers, payload, requestData);
- }
- return super.handleResponse(...arguments);
- }
-
- parseErrorOrResponse(errorOrResponse, secretDataBaseResponse, isSubkeys = false) {
- // if it's a legitimate error - throw it!
- if (errorOrResponse instanceof ControlGroupError) {
- throw errorOrResponse;
- }
-
- const errorCode = errorOrResponse.httpStatus;
- if (errorCode === 403) {
- return {
- data: {
- ...secretDataBaseResponse,
- fail_read_error_code: errorCode,
- },
- };
- }
-
- // in the case of a deleted/destroyed secret the API returns a 404 because { data: null }
- // however, there could be a metadata block with important information like deletion_time
- // handleResponse below checks 404 status codes for metadata and updates the code to 200 if it exists.
- // we still end up in the good ol' catch() block, but instead of a 404 adapter error we've "caught"
- // the metadata that sneakily tried to hide from us
- if (errorOrResponse.data) {
- // subkeys response doesn't correspond to a model, no need to include base response
- if (isSubkeys) return errorOrResponse.data;
-
- return {
- ...errorOrResponse,
- data: {
- ...secretDataBaseResponse,
- ...errorOrResponse.data, // includes the { metadata } key we want
- },
- };
- }
-
- // If we get here, it's probably a 404 because it doesn't exist
- throw errorOrResponse;
- }
-}
diff --git a/ui/app/adapters/kv/metadata.js b/ui/app/adapters/kv/metadata.js
deleted file mode 100644
index f87d341600..0000000000
--- a/ui/app/adapters/kv/metadata.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import ApplicationAdapter from '../application';
-import { kvMetadataPath } from 'vault/utils/kv-path';
-
-export default class KvMetadataAdapter extends ApplicationAdapter {
- namespace = 'v1';
-
- _url(fullPath) {
- return `${this.buildURL()}/${fullPath}`;
- }
-
- createRecord(store, type, snapshot) {
- const { backend, path } = snapshot.record;
- const id = kvMetadataPath(backend, path);
- const url = this._url(id);
- const data = this.serialize(snapshot);
- return this.ajax(url, 'POST', { data }).then(() => {
- return {
- id,
- data,
- };
- });
- }
-
- updateRecord(store, type, snapshot) {
- const { backend, path } = snapshot.record;
- const id = kvMetadataPath(backend, path);
- const url = this._url(id);
- const data = this.serialize(snapshot);
- return this.ajax(url, 'POST', { data }).then(() => {
- return {
- id,
- data,
- };
- });
- }
-
- query(store, type, query) {
- const { backend, pathToSecret } = query;
- // example of pathToSecret: beep/boop/
- return this.ajax(this._url(kvMetadataPath(backend, pathToSecret)), 'GET', {
- data: { list: true },
- }).then((resp) => {
- resp.backend = backend;
- resp.path = pathToSecret;
- return resp;
- });
- }
-
- queryRecord(store, type, query) {
- const { backend, path } = query;
- // ID is the full path for the metadata
- const id = kvMetadataPath(backend, path);
- return this.ajax(this._url(id), 'GET').then((resp) => {
- return {
- id,
- ...resp,
- data: {
- backend,
- path,
- ...resp.data,
- },
- };
- });
- }
-
- // This method is called when deleting from the list or metadata details view.
- // Otherwise, delete happens in kv/data adapter
- deleteRecord(store, type, snapshot) {
- const { backend, path, fullSecretPath } = snapshot.record;
- // fullSecretPath is used when deleting from the LIST view and is defined via the serializer
- // path is used when deleting from the metadata details view.
- return this.ajax(this._url(kvMetadataPath(backend, fullSecretPath || path)), 'DELETE');
- }
-
- // custom method used if users do not have "read" permissions to fetch record
- deleteMetadata(backend, path) {
- return this.ajax(this._url(kvMetadataPath(backend, path)), 'DELETE');
- }
-}
diff --git a/ui/app/app.js b/ui/app/app.js
index 6ae15248eb..09ddb5950d 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -105,7 +105,6 @@ export default class App extends Application {
'namespace',
{ 'app-router': 'router' },
'secret-mount-path',
- 'store',
'pagination',
'version',
],
diff --git a/ui/app/components/dashboard/quick-actions-card.hbs b/ui/app/components/dashboard/quick-actions-card.hbs
index 2e81fba965..a07458c44a 100644
--- a/ui/app/components/dashboard/quick-actions-card.hbs
+++ b/ui/app/components/dashboard/quick-actions-card.hbs
@@ -39,16 +39,16 @@
@ariaLabel="Action"
/>
- {{#if this.searchSelectParams.model}}
+ {{#if this.selectedAction}}
{{! use special input to allow searching for KVv2 secrets inside a directory }}
- {{#if (eq this.selectedEngine.type "kv")}}
+ {{#if this.searchSelectParams.isKV}}
- {{else}}
+ {{else if this.searchSelectParams.model}}
{{this.searchSelectParams.title}}
- model.secretData !== undefined && typeof model.secretData !== 'object' ? false : true,
- message: 'Vault expects data to be formatted as an JSON object.',
- },
- ],
-};
-@withModelValidations(validations)
-@withFormFields()
-export default class KvSecretDataModel extends Model {
- @attr('string') backend; // dynamic path of secret -- set on response from value passed to queryRecord.
- @attr('string', {
- label: 'Path for this secret',
- subText: 'Names with forward slashes define hierarchical path structures.',
- })
- path;
- @attr('object') secretData; // { key: value } data of the secret version
-
- // Params returned on the GET response.
- @attr('string') createdTime;
- @attr('object') customMetadata;
- @attr('string') deletionTime;
- @attr('boolean') destroyed;
- @attr('number') version;
- // Set in adapter if read failed
- @attr('number') failReadErrorCode;
-
- // if creating a new version this value is set in the edit route's
- // model hook from metadata or secret version, pending permissions
- // if the value is not a number, don't send options.cas on payload
- @attr('number')
- casVersion;
-
- get state() {
- if (this.destroyed) return 'destroyed';
- if (this.isSecretDeleted) return 'deleted';
- if (this.createdTime) return 'created';
- return '';
- }
-
- // cannot use isDeleted as model property name because of an ember property conflict
- get isSecretDeleted() {
- return isDeleted(this.deletionTime);
- }
-
- // Permissions
- @lazyCapabilities(apiPath`${'backend'}/data/${'path'}`, 'backend', 'path') dataPath;
- @lazyCapabilities(apiPath`${'backend'}/metadata/${'path'}`, 'backend', 'path') metadataPath;
- @lazyCapabilities(apiPath`${'backend'}/delete/${'path'}`, 'backend', 'path') deletePath;
- @lazyCapabilities(apiPath`${'backend'}/destroy/${'path'}`, 'backend', 'path') destroyPath;
- @lazyCapabilities(apiPath`${'backend'}/undelete/${'path'}`, 'backend', 'path') undeletePath;
- @lazyCapabilities(apiPath`${'backend'}/subkeys/${'path'}`, 'backend', 'path') subkeysPath;
-
- get canDeleteLatestVersion() {
- return this.dataPath.get('canDelete') !== false;
- }
- get canDeleteVersion() {
- return this.deletePath.get('canUpdate') !== false;
- }
- get canUndelete() {
- return this.undeletePath.get('canUpdate') !== false;
- }
- get canDestroyVersion() {
- return this.destroyPath.get('canUpdate') !== false;
- }
- get canEditData() {
- return this.dataPath.get('canUpdate') !== false;
- }
- get canReadData() {
- return this.dataPath.get('canRead') !== false;
- }
- get canReadMetadata() {
- return this.metadataPath.get('canRead') !== false;
- }
- get canUpdateMetadata() {
- return this.metadataPath.get('canUpdate') !== false;
- }
- get canListMetadata() {
- return this.metadataPath.get('canList') !== false;
- }
- get canDeleteMetadata() {
- return this.metadataPath.get('canDelete') !== false;
- }
- get canReadSubkeys() {
- return this.subkeysPath.get('canRead') !== false;
- }
-}
diff --git a/ui/app/models/kv/metadata.js b/ui/app/models/kv/metadata.js
deleted file mode 100644
index 44fe62dd35..0000000000
--- a/ui/app/models/kv/metadata.js
+++ /dev/null
@@ -1,118 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-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/helpers/is-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.' },
- ],
-};
-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;
-
- // the API returns custom_metadata: null if empty but because the attr is an 'object' ember data transforms it to an empty object.
- // this is important because we rely on the empty object as a truthy value in template conditionals
- @attr('object', {
- editType: 'kv',
- isSectionHeader: true,
- 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);
- }
-
- // 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',
- deletionTime: data.deletion_time,
- };
- }
-
- 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/${'permissionsPath'}`, 'backend', 'permissionsPath') dataPath;
- @lazyCapabilities(apiPath`${'backend'}/metadata/${'permissionsPath'}`, 'backend', 'permissionsPath')
- metadataPath;
-
- 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;
- }
-}
diff --git a/ui/app/serializers/kv/data.js b/ui/app/serializers/kv/data.js
deleted file mode 100644
index 1e75ac21aa..0000000000
--- a/ui/app/serializers/kv/data.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import ApplicationSerializer from '../application';
-
-export default class KvDataSerializer extends ApplicationSerializer {
- serialize(snapshot) {
- const { secretData, casVersion } = snapshot.record;
- if (typeof casVersion === 'number') {
- /* if this is a number it is set by one of the following:
- A) user is creating initial version of a secret
- -> 0 : default value set in route
- B) user is creating a new version of a secret:
- -> metadata.current_version : has metadata read permissions (data permissions are irrelevant)
- -> secret.version : has data read permissions. without metadata read access a user is unable to navigate,
- to older secret versions so we assume creation is from the latest version */
- return { data: secretData, options: { cas: casVersion } };
- }
- // a non-number value means no read permission for both data and metadata
- return { data: secretData };
- }
-
- normalizeKvData(payload) {
- const { data, metadata } = payload.data;
- return {
- ...payload,
- data: {
- ...payload.data,
- // Rename to secret_data so it doesn't get removed by normalizer
- secret_data: data,
- ...metadata,
- },
- };
- }
-
- normalizeResponse(store, primaryModelClass, payload, id, requestType) {
- if (requestType === 'queryRecord') {
- const transformed = this.normalizeKvData(payload);
- return super.normalizeResponse(store, primaryModelClass, transformed, id, requestType);
- }
- return super.normalizeResponse(store, primaryModelClass, payload, id, requestType);
- }
-}
diff --git a/ui/app/serializers/kv/metadata.js b/ui/app/serializers/kv/metadata.js
deleted file mode 100644
index 7d5b4f16b5..0000000000
--- a/ui/app/serializers/kv/metadata.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { assert } from '@ember/debug';
-import ApplicationSerializer from '../application';
-import { kvMetadataPath } from 'vault/utils/kv-path';
-
-export default class KvMetadataSerializer extends ApplicationSerializer {
- attrs = {
- backend: { serialize: false },
- path: { serialize: false },
- oldestVersion: { serialize: false },
- createdTime: { serialize: false },
- updatedTime: { serialize: false },
- currentVersion: { serialize: false },
- versions: { serialize: false },
- };
-
- normalizeItems(payload) {
- if (payload.data.keys) {
- assert('payload.backend must be provided on kv/metadata list response', !!payload.backend);
- return payload.data.keys.map((secret) => {
- // If there is no payload.path then we're either on a "top level" secret or the first level directory of a nested secret.
- // We set the path to the current secret or pathToSecret. e.g. my-secret or beep/boop/
- // We add a param called full_secret_path to the model which we use to navigate to the nested secret. e.g. beep/boop/bop.
- const fullSecretPath = payload.path ? payload.path + secret : secret;
- return {
- id: kvMetadataPath(payload.backend, fullSecretPath),
- path: secret,
- backend: payload.backend,
- full_secret_path: fullSecretPath,
- };
- });
- }
- return super.normalizeItems(payload);
- }
-}
diff --git a/ui/lib/core/addon/components/kv-suggestion-input.hbs b/ui/lib/core/addon/components/kv-suggestion-input.hbs
index 37e66737e2..d1899115db 100644
--- a/ui/lib/core/addon/components/kv-suggestion-input.hbs
+++ b/ui/lib/core/addon/components/kv-suggestion-input.hbs
@@ -29,6 +29,6 @@
data-test-kv-suggestion-select
as |secret|
>
- {{secret.path}}
+ {{secret}}
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/kv-suggestion-input.ts b/ui/lib/core/addon/components/kv-suggestion-input.ts
index bd151e89c7..1522d74b35 100644
--- a/ui/lib/core/addon/components/kv-suggestion-input.ts
+++ b/ui/lib/core/addon/components/kv-suggestion-input.ts
@@ -10,9 +10,9 @@ import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import { run } from '@ember/runloop';
import { keyIsFolder, parentKeyForKey, keyWithoutParentKey } from 'core/utils/key-utils';
+import { KvV2ListListEnum } from '@hashicorp/vault-client-typescript';
-import type Store from '@ember-data/store';
-import type KvSecretMetadataModel from 'vault/models/kv/metadata';
+import type ApiService from 'vault/services/api';
/**
* @module KvSuggestionInput
@@ -49,12 +49,13 @@ interface PowerSelectAPI {
}
export default class KvSuggestionInputComponent extends Component {
- @service declare readonly store: Store;
+ @service declare readonly api: ApiService;
- @tracked secrets: KvSecretMetadataModel[] = [];
+ @tracked secrets: string[] = [];
powerSelectAPI: PowerSelectAPI | undefined;
- _cachedSecrets: KvSecretMetadataModel[] = []; // cache the response for filtering purposes
+ _cachedSecrets: string[] = []; // cache the response for filtering purposes
inputId = `suggestion-input-${guidFor(this)}`; // add unique segment to id in case multiple instances of component are used on the same page
+ pathToSecret = ''; // keeps track of the full path to the secret as user builds it out
constructor(owner: unknown, args: Args) {
super(owner, args);
@@ -63,18 +64,19 @@ export default class KvSuggestionInputComponent extends Component {
}
}
- async fetchSecrets(isDirectory: boolean) {
- const { mountPath } = this.args;
+ get isDirectory() {
+ return keyIsFolder(this.args.value);
+ }
+
+ async fetchSecrets() {
try {
+ const { mountPath } = this.args;
const backend = keyIsFolder(mountPath) ? mountPath.slice(0, -1) : mountPath;
const parentDirectory = parentKeyForKey(this.args.value);
- const pathToSecret = isDirectory ? this.args.value : parentDirectory;
- const kvModels = (await this.store.query('kv/metadata', {
- backend,
- pathToSecret,
- })) as unknown;
+ this.pathToSecret = this.isDirectory ? this.args.value : parentDirectory;
+ const { keys } = await this.api.secrets.kvV2List(this.pathToSecret, backend, KvV2ListListEnum.TRUE);
// this will be used to filter the existing result set when the search term changes within the same path
- this._cachedSecrets = kvModels as KvSecretMetadataModel[];
+ this._cachedSecrets = keys || [];
return this._cachedSecrets;
} catch (error) {
console.log(error); // eslint-disable-line
@@ -82,33 +84,32 @@ export default class KvSuggestionInputComponent extends Component {
}
}
- filterSecrets(kvModels: KvSecretMetadataModel[] | undefined = [], isDirectory: boolean) {
+ filterSecrets(secrets: string[] | undefined = []) {
const { value } = this.args;
const secretName = keyWithoutParentKey(value) || '';
- return kvModels.filter((model) => {
- if (!value || isDirectory) {
+ return secrets.filter((path) => {
+ if (!value || this.isDirectory) {
return true;
}
- if (value === model.fullSecretPath) {
+ if (secretName === path) {
// don't show suggestion if it's currently selected
return false;
}
- return model.path.toLowerCase().includes(secretName.toLowerCase());
+ return path.toLowerCase().includes(secretName.toLowerCase());
});
}
@action
async updateSuggestions() {
const isFirstUpdate = !this._cachedSecrets.length;
- const isDirectory = keyIsFolder(this.args.value);
if (!this.args.mountPath) {
this.secrets = [];
- } else if (this.args.value && !isDirectory && this.secrets) {
+ } else if (this.args.value && !this.isDirectory && this.secrets) {
// if we don't need to fetch from a new path, filter the previous result set with the updated search term
- this.secrets = this.filterSecrets(this._cachedSecrets, isDirectory);
+ this.secrets = this.filterSecrets(this._cachedSecrets);
} else {
- const kvModels = await this.fetchSecrets(isDirectory);
- this.secrets = this.filterSecrets(kvModels, isDirectory);
+ const secrets = await this.fetchSecrets();
+ this.secrets = this.filterSecrets(secrets);
}
// don't do anything on first update -- allow dropdown to open on input click
if (!isFirstUpdate) {
@@ -131,11 +132,11 @@ export default class KvSuggestionInputComponent extends Component {
}
@action
- onSuggestionSelect(secret: KvSecretMetadataModel) {
+ onSuggestionSelect(secret: string) {
// user may partially type a value to filter result set and then select a suggestion
// in this case the partially typed value must be replaced with suggestion value
- // the fullSecretPath contains the previous selections or typed path segments
- this.args.onChange(secret.fullSecretPath);
+ // pathToSecret contains the previous selections or typed path segments
+ this.args.onChange(`${this.pathToSecret}${secret}`);
this.updateSuggestions();
// refocus the input after selection
run(() => document.getElementById(this.inputId)?.focus());
diff --git a/ui/lib/kv/addon/components/page/list.js b/ui/lib/kv/addon/components/page/list.js
index bb96dcd3e2..018055abec 100644
--- a/ui/lib/kv/addon/components/page/list.js
+++ b/ui/lib/kv/addon/components/page/list.js
@@ -13,9 +13,9 @@ import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
/**
* @module List
- * ListPage component is a component to show a list of kv/metadata secrets.
+ * ListPage component is a component to show a list of secrets.
*
- * @param {array} secrets - An array of models generated form kv/metadata query.
+ * @param {array} secrets - An array of secrets
* @param {string} backend - The name of the kv secret engine.
* @param {string} pathToSecret - The directory name that the secret belongs to ex: beep/boop/
* @param {string} filterValue - The concatenation of the pathToSecret and pageFilter ex: beep/boop/my-
diff --git a/ui/lib/kv/addon/components/page/secret/metadata/details.hbs b/ui/lib/kv/addon/components/page/secret/metadata/details.hbs
index 09aaa70c50..f9c90cd043 100644
--- a/ui/lib/kv/addon/components/page/secret/metadata/details.hbs
+++ b/ui/lib/kv/addon/components/page/secret/metadata/details.hbs
@@ -56,51 +56,53 @@
@title="You do not have access to read custom metadata"
@message="In order to read custom metadata you either need read access to the secret data and/or read access to metadata."
/>
- {{else if this.canRequestData}}
+ {{else}}
{{! Offer opportunity to manually request /data/ for custom_metadata }}
{{#if this.error.isControlGroup}}
{{else if this.error}}
{{/if}}
-
-
-
-
- Sensitive secret data will be retrieved.
-
-
-
-
-
- {{else}}
-
- {{#if @capabilities.canUpdateMetadata}}
-
- {{/if}}
-
+ {{#if this.canRequestData}}
+
+
+
+
+ Sensitive secret data will be retrieved.
+
+
+
+
+
+ {{else}}
+
+ {{#if @capabilities.canUpdateMetadata}}
+
+ {{/if}}
+
+ {{/if}}
{{/if}}
{{/each-in}}
diff --git a/ui/lib/kv/addon/components/page/secret/metadata/details.js b/ui/lib/kv/addon/components/page/secret/metadata/details.js
index 5c84ded817..d19f6a67ee 100644
--- a/ui/lib/kv/addon/components/page/secret/metadata/details.js
+++ b/ui/lib/kv/addon/components/page/secret/metadata/details.js
@@ -8,9 +8,10 @@ import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import errorMessage from 'vault/utils/error-message';
+import { waitFor } from '@ember/test-waiters';
/**
- * @module KvSecretMetadataDetails renders the details view for kv/metadata and button to delete (which deletes the whole secret) or edit metadata.
+ * @module KvSecretMetadataDetails renders the details view for kv metadata and button to delete (which deletes the whole secret) or edit metadata.
* {
- assert(
- `'baseSetup()' requires mirage: import { setupMirage } from 'ember-cli-mirage/test-support'`,
- context.server
- );
- context.store = context.owner.lookup('service:store');
- context.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
- context.backend = 'kv-engine';
- context.path = 'my-secret';
- context.metadata = metadataModel(context, { withCustom: false });
-};
-
-export const metadataModel = (context, { withCustom = false }) => {
- const metadata = withCustom
- ? context.server.create('kv-metadatum', 'withCustomMetadata')
- : context.server.create('kv-metadatum');
- metadata.id = kvMetadataPath(context.backend, context.path);
- context.store.pushPayload('kv/metadata', {
- modelName: 'kv/metadata',
- ...metadata,
- });
- return context.store.peekRecord('kv/metadata', metadata.id);
-};
diff --git a/ui/tests/integration/components/kv-suggestion-input-test.js b/ui/tests/integration/components/kv-suggestion-input-test.js
index d75cc31de6..5af31e929f 100644
--- a/ui/tests/integration/components/kv-suggestion-input-test.js
+++ b/ui/tests/integration/components/kv-suggestion-input-test.js
@@ -5,10 +5,10 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { render, click, fillIn, settled, typeIn } from '@ember/test-helpers';
+import { render, click, fillIn, typeIn } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import sinon from 'sinon';
const {
searchSelect: { option, options },
@@ -17,16 +17,16 @@ const {
module('Integration | Component | kv-suggestion-input', function (hooks) {
setupRenderingTest(hooks);
- setupMirage(hooks);
hooks.beforeEach(function () {
this.label = 'Input something';
this.mountPath = 'secret/';
- this.keys = ['foo/', 'my-secret'];
- const response = () => ({ data: { keys: this.keys } });
- this.server.get('/:mount/metadata', response);
- this.server.get('/:mount/metadata/*', response);
- return render(hbs`
+ this.apiStub = sinon
+ .stub(this.owner.lookup('service:api').secrets, 'kvV2List')
+ .resolves({ keys: ['foo/', 'my-secret'] });
+
+ this.renderComponent = () =>
+ render(hbs`
{
@@ -59,14 +63,15 @@ module('Integration | Component | kv-suggestion-input', function (hooks) {
});
test('it should fetch secrets and update suggestions on mountPath change', async function (assert) {
- this.keys = ['test1'];
- this.set('mountPath', 'foo/');
- await settled();
+ this.apiStub.resolves({ keys: ['test1'] });
+ this.mountPath = 'foo/';
+ await this.renderComponent();
await click(input);
assert.dom(option()).hasText('test1', 'Suggestions are fetched and render on mountPath change');
});
test('it should filter current result set', async function (assert) {
+ await this.renderComponent();
await click(input);
await typeIn(input, 'sec');
assert.dom(options).exists({ count: 1 }, 'Correct number of options render based on input value');
@@ -74,12 +79,13 @@ module('Integration | Component | kv-suggestion-input', function (hooks) {
});
test('it should replace filter terms with full path to secret', async function (assert) {
+ await this.renderComponent();
await fillIn(input, 'sec');
await click(option());
assert.dom(input).hasValue('my-secret', 'Partial term replaced with selected secret');
await fillIn(input, '');
- this.keys = ['secret-nested', 'bar', 'baz'];
+ this.apiStub.resolves({ keys: ['secret-nested', 'bar', 'baz'] });
await click(option());
await fillIn(input, 'nest');
await click(option());
@@ -89,23 +95,25 @@ module('Integration | Component | kv-suggestion-input', function (hooks) {
});
test('it should fetch secrets at nested paths', async function (assert) {
- this.keys = ['bar/'];
+ await this.renderComponent();
+ this.apiStub.resolves({ keys: ['bar/'] });
await click(input);
await click(option());
assert.dom(input).hasValue('foo/', 'Input value updates on select');
assert.dom(option()).hasText('bar/', 'Suggestions are fetched at new path');
- this.keys = ['baz/'];
+ this.apiStub.resolves({ keys: ['baz/'] });
await click(option());
assert.dom(input).hasValue('foo/bar/', 'Input value updates on select');
assert.dom(option()).hasText('baz/', 'Suggestions are fetched at new path');
- this.keys = ['nested-secret'];
+ this.apiStub.resolves({ keys: ['nested-secret'] });
await typeIn(input, 'baz/');
assert.dom(option()).hasText('nested-secret', 'Suggestions are fetched at new path');
});
test('it should only render dropdown when suggestions exist', async function (assert) {
+ await this.renderComponent();
await click(input);
assert.dom(options).exists({ count: 2 }, 'Suggestions render');
@@ -115,7 +123,7 @@ module('Integration | Component | kv-suggestion-input', function (hooks) {
await fillIn(input, '');
assert.dom(options).exists({ count: 2 }, 'Suggestions render');
- this.keys = [];
+ this.apiStub.resolves({ keys: [] });
await click(option());
assert.dom(options).doesNotExist('Drop down is hidden when there are no suggestions');
});
diff --git a/ui/tests/integration/components/kv/kv-page-header-test.js b/ui/tests/integration/components/kv/kv-page-header-test.js
index 7f19e27975..1763b51144 100644
--- a/ui/tests/integration/components/kv/kv-page-header-test.js
+++ b/ui/tests/integration/components/kv/kv-page-header-test.js
@@ -6,46 +6,54 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { setupEngine } from 'ember-engines/test-support';
-import { setupMirage } from 'ember-cli-mirage/test-support';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
-import { kvDataPath } from 'vault/utils/kv-path';
module('Integration | Component | kv | kv-page-header', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'kv');
- setupMirage(hooks);
+
hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
this.backend = 'kv-engine';
this.path = 'my-secret';
- this.version = 2;
- this.id = kvDataPath(this.backend, this.path, this.version);
- this.payload = {
- backend: this.backend,
- path: this.path,
- version: 2,
- };
- this.store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: this.id,
- ...this.payload,
- });
- this.model = this.store.peekRecord('kv/data', this.id);
this.breadcrumbs = [
{ label: 'Secrets', route: 'secrets', linkExternal: true },
- { label: this.model.backend, route: 'secrets' },
- { label: this.model.path, route: 'secrets.secret.details', model: this.model.path },
+ { label: this.backend, route: 'secrets' },
+ { label: this.path, route: 'secrets.secret.details', model: this.path },
{ label: 'Edit' },
];
+
+ this.renderComponent = () =>
+ render(
+ hbs`
+
+ <:tabLinks>
+ Secrets
+ Configuration
+
+
+ <:toolbarActions>
+ Create secret
+
+
+ <:toolbarFilters>
+ stuff here
+
+
+ `,
+ { owner: this.engine }
+ );
});
test('it renders breadcrumbs', async function (assert) {
assert.expect(4);
- await render(hbs``, {
- owner: this.engine,
- });
+ await this.renderComponent();
assert.dom('[data-test-breadcrumbs] li:nth-child(1) a').hasText('Secrets', 'Secrets breadcrumb renders');
assert.dom('[data-test-breadcrumbs] li:nth-child(2) a').hasText(this.backend, 'engine name renders');
assert.dom('[data-test-breadcrumbs] li:nth-child(3) a').hasText(this.path, 'secret path renders');
@@ -56,17 +64,16 @@ module('Integration | Component | kv | kv-page-header', function (hooks) {
test('it renders a custom title for @pageTitle', async function (assert) {
assert.expect(2);
- await render(hbs``, {
- owner: this.engine,
- });
+ this.pageTitle = 'Create new version';
+ await this.renderComponent();
assert.dom('[data-test-header-title]').hasText('Create new version', 'displays custom title.');
assert.dom('[data-test-header-title] svg').doesNotExist('Does not show icon if not at engine level.');
});
test('it renders a title and copy button for @secretPath', async function (assert) {
- await render(hbs``, {
- owner: this.engine,
- });
+ assert.expect(3);
+ this.secretPath = 'my/secret/path';
+ await this.renderComponent();
assert.dom('[data-test-header-title]').hasText('my/secret/path', 'displays path');
assert.dom('[data-test-header-title] button').exists('renders copy button for path');
assert.dom('[data-test-icon="clipboard-copy"]').exists('renders copy icon');
@@ -74,9 +81,8 @@ module('Integration | Component | kv | kv-page-header', function (hooks) {
test('it renders a title, icon and tag if engine view', async function (assert) {
assert.expect(2);
- await render(hbs``, {
- owner: this.engine,
- });
+ this.mountName = this.backend;
+ await this.renderComponent();
assert
.dom('[data-test-header-title]')
.hasText(`${this.backend} version 2`, 'Mount path and version tag render for title.');
@@ -87,18 +93,7 @@ module('Integration | Component | kv | kv-page-header', function (hooks) {
test('it renders tabs', async function (assert) {
assert.expect(2);
- await render(
- hbs`
- <:tabLinks>
- Secrets
- Configuration
-
-
- `,
- {
- owner: this.engine,
- }
- );
+ await this.renderComponent();
assert.dom('[data-test-secrets-tab="Secrets"]').hasText('Secrets', 'Secrets tab renders');
assert
.dom('[data-test-secrets-tab="Configuration"]')
@@ -107,29 +102,13 @@ module('Integration | Component | kv | kv-page-header', function (hooks) {
test('it should yield block for toolbar actions', async function (assert) {
assert.expect(1);
- await render(
- hbs`
- <:toolbarActions>
- Create secret
-
-
- `,
- { owner: this.engine }
- ),
- assert.dom('.toolbar-actions').exists('Block is yielded for toolbar actions');
+ await this.renderComponent();
+ assert.dom('.toolbar-actions').exists('Block is yielded for toolbar actions');
});
test('it should yield block for toolbar filters', async function (assert) {
assert.expect(1);
- await render(
- hbs`
- <:toolbarFilters>
- stuff here
-
-
- `,
- { owner: this.engine }
- ),
- assert.dom('.toolbar-filters').exists('Block is yielded for toolbar filters');
+ await this.renderComponent();
+ assert.dom('.toolbar-filters').exists('Block is yielded for toolbar filters');
});
});
diff --git a/ui/tests/integration/components/sync/secrets/page/destinations/destination/sync-test.js b/ui/tests/integration/components/sync/secrets/page/destinations/destination/sync-test.js
index 1146bf9097..a314d469c3 100644
--- a/ui/tests/integration/components/sync/secrets/page/destinations/destination/sync-test.js
+++ b/ui/tests/integration/components/sync/secrets/page/destinations/destination/sync-test.js
@@ -14,6 +14,7 @@ import { PAGE } from 'vault/tests/helpers/sync/sync-selectors';
import { selectChoose } from 'ember-power-select/test-support';
import { Response } from 'miragejs';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import sinon from 'sinon';
const { destinations, searchSelect, messageError, kvSuggestion } = PAGE;
const { mountSelect, mountInput, successMessage } = destinations.sync;
@@ -25,15 +26,12 @@ module('Integration | Component | sync | Secrets::Page::Destinations::Destinatio
setupDataStubs(hooks);
hooks.beforeEach(async function () {
- this.server.get('/sys/internal/ui/mounts', () => ({
- data: { secret: { 'my-kv/': { type: 'kv', options: { version: '2' } } } },
- }));
- this.server.get('/my-kv/metadata', () => ({
- data: { keys: ['my-path/', 'my-secret'] },
- }));
- this.server.get('/my-kv/metadata/my-path', () => ({
- data: { keys: ['nested-secret'] },
- }));
+ const api = this.owner.lookup('service:api');
+ this.mountsStub = sinon
+ .stub(api.sys, 'internalUiListEnabledVisibleMounts')
+ .resolves({ secret: { 'my-kv/': { type: 'kv', options: { version: '2' } } } });
+
+ this.secretsStub = sinon.stub(api.secrets, 'kvV2List').resolves({ keys: ['my-path/', 'my-secret'] });
await render(
hbs``,
@@ -62,6 +60,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::Destinatio
test('it should render secret suggestions for nested paths', async function (assert) {
await selectChoose(mountSelect, '.ember-power-select-option', 1);
+ this.secretsStub.resolves({ keys: ['nested-secret'] });
await click(kvSuggestion.input);
await click(searchSelect.option());
assert
@@ -99,9 +98,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::Destinatio
test('it should allow manual mount path input if kv mounts are not returned', async function (assert) {
assert.expect(1);
- this.server.get('/sys/internal/ui/mounts', () => ({
- data: { secret: { 'cubbyhole/': { type: 'cubbyhole' } } },
- }));
+ this.mountsStub.resolves({ secret: { 'cubbyhole/': { type: 'cubbyhole' } } });
const { type, name } = this.destination;
this.server.post(`/sys/sync/destinations/${type}/${name}/associations/set`, (schema, req) => {
diff --git a/ui/tests/unit/adapters/kv/data-test.js b/ui/tests/unit/adapters/kv/data-test.js
deleted file mode 100644
index 2124fb6ef2..0000000000
--- a/ui/tests/unit/adapters/kv/data-test.js
+++ /dev/null
@@ -1,469 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { kvDataPath } from 'vault/utils/kv-path';
-import { encodePath } from 'vault/utils/path-encoding-helpers';
-import { Response } from 'miragejs';
-
-const EXAMPLE_KV_DATA_CREATE_RESPONSE = {
- request_id: 'foobar',
- data: {
- created_time: '2023-06-21T16:18:31.479993Z',
- custom_metadata: null,
- deletion_time: '',
- destroyed: false,
- version: 1,
- },
-};
-
-const EXAMPLE_KV_DATA_GET_RESPONSE = {
- request_id: 'foobar',
- data: {
- data: { foo: 'bar' },
- metadata: {
- created_time: '2023-06-20T21:26:47.592306Z',
- custom_metadata: null,
- deletion_time: '',
- destroyed: false,
- version: 2,
- },
- },
-};
-
-const EXAMPLE_KV_SUBKEYS_RESPONSE = {
- request_id: 'foobar',
- data: {
- ...EXAMPLE_KV_DATA_GET_RESPONSE,
- data: { foo: null },
- },
-};
-
-const EXAMPLE_CONTROL_GROUP_RESPONSE = {
- data: null,
- wrap_info: {
- token: 'some-token',
- accessor: 'some-accessor',
- ttl: 86400,
- creation_time: '2023-08-09T16:08:06-05:00',
- creation_path: 'some/path/here',
- },
-};
-
-const EXAMPLE_KV_DATA_DESTROYED = {
- data: {
- data: null,
- metadata: {
- created_time: '2023-08-09T20:10:24.4825Z',
- custom_metadata: null,
- deletion_time: '',
- destroyed: true,
- version: 2,
- },
- },
-};
-
-const EXAMPLE_KV_DATA_DELETED = {
- data: {
- data: null,
- metadata: {
- created_time: '2023-08-09T20:10:24.571332Z',
- custom_metadata: null,
- deletion_time: '2023-08-09T20:10:24.70176Z',
- destroyed: false,
- version: 2,
- },
- },
-};
-
-module('Unit | Adapter | kv/data', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'my/kv-back&end';
- this.secretMountPath.currentPath = this.backend;
- this.path = 'beep/bop/my secret';
- this.version = '2';
- this.id = kvDataPath(this.backend, this.path, this.version);
- this.data = {
- options: {
- cas: 2,
- },
- data: {
- foo: 'bar',
- },
- };
- this.payload = {
- backend: this.backend,
- path: this.path,
- version: 2,
- };
- this.endpoint = (noun) => `${encodePath(this.backend)}/${noun}/${encodePath(this.path)}`;
- });
-
- module('createRecord', function () {
- test('it should make request to correct endpoint on createRecord', async function (assert) {
- assert.expect(8);
- this.server.post(this.endpoint('data'), (schema, req) => {
- assert.ok('POST request made to correct endpoint when creating new record');
- const body = JSON.parse(req.requestBody);
- assert.deepEqual(body, {
- data: {
- foo: 'bar',
- },
- options: {
- cas: 0,
- },
- });
- return EXAMPLE_KV_DATA_CREATE_RESPONSE;
- });
- const record = this.store.createRecord('kv/data', {
- backend: this.backend,
- path: this.path,
- secretData: { foo: 'bar' },
- casVersion: 0,
- });
- await record.save();
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 1, 'record has correct version');
- assert.deepEqual(record.secretData, { foo: 'bar' }, 'record has correct data');
- assert.strictEqual(record.createdTime, '2023-06-21T16:18:31.479993Z', 'record has correct createdTime');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=1`,
- 'record has correct id'
- );
- });
-
- test('it should not send cas if casVersion is not a number', async function (assert) {
- assert.expect(8);
- this.server.post(this.endpoint('data'), (schema, req) => {
- assert.ok('POST request made to correct endpoint when creating new record');
- const body = JSON.parse(req.requestBody);
- assert.deepEqual(body, {
- data: {
- foo: 'bar',
- },
- });
- return EXAMPLE_KV_DATA_CREATE_RESPONSE;
- });
- const record = this.store.createRecord('kv/data', {
- backend: this.backend,
- path: this.path,
- secretData: { foo: 'bar' },
- });
- await record.save();
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 1, 'record has correct version');
- assert.deepEqual(record.secretData, { foo: 'bar' }, 'record has correct data');
- assert.strictEqual(record.createdTime, '2023-06-21T16:18:31.479993Z', 'record has correct createdTime');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=1`,
- 'record has correct id'
- );
- });
- });
-
- module('queryRecord', function () {
- test('it should make request to correct endpoint on queryRecord', async function (assert) {
- assert.expect(8);
- this.server.get(this.endpoint('data'), (schema, req) => {
- assert.ok(true, 'request is made to correct url on queryRecord.');
- assert.strictEqual(
- req.queryParams.version,
- this.version,
- 'request includes the version flag on queryRecord.'
- );
- return EXAMPLE_KV_DATA_GET_RESPONSE;
- });
-
- const record = await this.store.queryRecord('kv/data', this.payload);
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 2, 'record has correct version');
- assert.deepEqual(record.secretData, { foo: 'bar' }, 'record has correct data');
- assert.strictEqual(record.createdTime, '2023-06-20T21:26:47.592306Z', 'record has correct createdTime');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=${this.version}`,
- 'record has correct id'
- );
- });
-
- test('it should handle a 404 not found response properly', async function (assert) {
- assert.expect(1);
- this.server.get(this.endpoint('data'), () => {
- // This is what the API currently returns for not found
- return new Response(404, {}, { errors: [] });
- });
-
- try {
- await this.store.queryRecord('kv/data', this.payload);
- } catch (e) {
- assert.ok('throws the error');
- }
- });
-
- test('it should handle 404 for a soft-deleted version properly', async function (assert) {
- this.server.get(this.endpoint('data'), () => {
- return new Response(404, {}, EXAMPLE_KV_DATA_DELETED);
- });
-
- const record = await this.store.queryRecord('kv/data', this.payload);
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 2, 'record has version based on request');
- assert.strictEqual(record.deletionTime, '2023-08-09T20:10:24.70176Z', 'record includes deletion time');
- assert.strictEqual(record.failReadErrorCode, undefined, 'record does not have failed error code');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=${this.version}`,
- 'record has correct id'
- );
- });
-
- test('it should handle 404 for a destroyed version properly', async function (assert) {
- this.server.get(this.endpoint('data'), () => {
- return new Response(404, {}, EXAMPLE_KV_DATA_DESTROYED);
- });
-
- const record = await this.store.queryRecord('kv/data', this.payload);
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 2, 'record has version based on request');
- assert.true(record.destroyed, 'record has destroyed value');
- assert.strictEqual(record.failReadErrorCode, undefined, 'record does not have error code');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=${this.version}`,
- 'record has correct id'
- );
- });
-
- test('it should handle a 403 permission denied properly', async function (assert) {
- assert.expect(8);
- this.server.get(this.endpoint('data'), (schema, req) => {
- assert.ok(true, 'request is made to correct url on queryRecord.');
- assert.strictEqual(
- req.queryParams.version,
- this.version,
- 'request includes the version flag on queryRecord.'
- );
- return new Response(403, {}, { errors: ['1 error occurred:\n\t* permission denied\n\n'] });
- });
-
- const record = await this.store.queryRecord('kv/data', this.payload);
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.version, 2, 'record has version based on request');
- assert.strictEqual(record.secretData, undefined, 'record does not include data');
- assert.strictEqual(record.failReadErrorCode, 403, 'record has error response recorded');
- assert.strictEqual(
- record.id,
- `${encodePath(this.backend)}/data/${encodePath(this.path)}?version=${this.version}`,
- 'record has correct id'
- );
- });
-
- test('it should handle a control group response properly', async function (assert) {
- assert.expect(1);
- this.owner.lookup('service:version').type = 'enterprise'; // Required for testing control-group flow
- this.server.get(this.endpoint('data'), () => {
- return EXAMPLE_CONTROL_GROUP_RESPONSE;
- });
-
- try {
- await this.store.queryRecord('kv/data', this.payload);
- } catch (e) {
- assert.ok('throws the error');
- }
- });
- });
-
- module('destroyRecord', function () {
- test('it should make request to correct endpoint on delete latest version', async function (assert) {
- assert.expect(3);
- this.server.delete(this.endpoint('data'), () => {
- assert.ok(true, 'request made to correct endpoint on delete latest version.');
- return new Response(204);
- });
-
- this.store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: this.id,
- ...this.payload,
- });
- let record = await this.store.peekRecord('kv/data', this.id);
- await record.destroyRecord({ adapterOptions: { deleteType: 'delete-latest-version' } });
- assert.true(record.isDeleted, 'record is deleted');
- record = await this.store.peekRecord('kv/data', this.id);
- assert.strictEqual(record, null, 'record is no longer in store');
- });
-
- test('it should make request to correct endpoint on delete specific versions', async function (assert) {
- assert.expect(4);
- this.server.post(this.endpoint('delete'), (schema, req) => {
- const { versions } = JSON.parse(req.requestBody);
- assert.strictEqual(versions, 2, 'version array is sent in the payload.');
- assert.ok(true, 'request made to correct endpoint on delete specific version.');
- });
-
- this.store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: this.id,
- ...this.payload,
- });
- let record = await this.store.peekRecord('kv/data', this.id);
- await record.destroyRecord({
- adapterOptions: { deleteType: 'delete-version', deleteVersions: 2 },
- });
- assert.true(record.isDeleted, 'record is deleted');
- record = await this.store.peekRecord('kv/data', this.id);
- assert.strictEqual(record, null, 'record is no longer in store');
- });
-
- test('it should make request to correct endpoint on undelete', async function (assert) {
- assert.expect(4);
- this.server.post(`${this.backend}/undelete/${this.path}`, (schema, req) => {
- const { versions } = JSON.parse(req.requestBody);
- assert.strictEqual(versions, 2, 'version array is sent in the payload.');
- assert.ok(true, 'request made to correct endpoint on undelete specific version.');
- });
-
- this.store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: this.id,
- ...this.payload,
- });
- let record = await this.store.peekRecord('kv/data', this.id);
-
- await record.destroyRecord({
- adapterOptions: { deleteType: 'undelete', deleteVersions: 2 },
- });
- assert.true(record.isDeleted, 'record is deleted');
- record = await this.store.peekRecord('kv/data', this.id);
- assert.strictEqual(record, null, 'record is no longer in store');
- });
-
- test('it should make request to correct endpoint on destroy specific versions', async function (assert) {
- assert.expect(4);
- this.server.put(`${encodePath(this.backend)}/destroy/${encodePath(this.path)}`, (schema, req) => {
- const { versions } = JSON.parse(req.requestBody);
- assert.strictEqual(versions, 2, 'version array is sent in the payload.');
- assert.ok(true, 'request made to correct endpoint on destroy specific version.');
- });
-
- this.store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: this.id,
- ...this.payload,
- });
- let record = await this.store.peekRecord('kv/data', this.id);
- await record.destroyRecord({
- adapterOptions: { deleteType: 'destroy', deleteVersions: 2 },
- });
- assert.true(record.isDeleted, 'record is deleted');
- record = await this.store.peekRecord('kv/data', this.id);
- assert.strictEqual(record, null, 'record is no longer in store');
- });
- });
-
- module('fetchSubkeys', function (hooks) {
- hooks.beforeEach(function () {
- this.adapter = this.store.adapterFor('kv/data');
- this.subkeysUrl = `${encodePath(this.backend)}/subkeys/${encodePath(this.path)}`;
- });
-
- test('it should make request with no query', async function (assert) {
- assert.expect(2);
- const expectedQuery = {};
- this.server.get(this.subkeysUrl, (schema, { queryParams }) => {
- assert.true(true, `GET request made to ${this.subkeysUrl}`);
- assert.propEqual(queryParams, expectedQuery, `queryParams contain: ${JSON.stringify(queryParams)}`);
- return EXAMPLE_KV_SUBKEYS_RESPONSE;
- });
-
- this.adapter.fetchSubkeys(this.backend, this.path);
- });
-
- test('it should make request with version param', async function (assert) {
- assert.expect(1);
- const query = { version: '2' };
- this.server.get(this.subkeysUrl, (schema, { queryParams }) => {
- assert.propEqual(queryParams, query, `queryParams contain: ${JSON.stringify(queryParams)}`);
- return EXAMPLE_KV_SUBKEYS_RESPONSE;
- });
-
- this.adapter.fetchSubkeys(this.backend, this.path, query);
- });
-
- test('it should make request with depth param', async function (assert) {
- assert.expect(1);
- const query = { depth: '1' };
- this.server.get(this.subkeysUrl, (schema, { queryParams }) => {
- assert.propEqual(queryParams, query, `queryParams contain: ${JSON.stringify(queryParams)}`);
- return EXAMPLE_KV_SUBKEYS_RESPONSE;
- });
-
- this.adapter.fetchSubkeys(this.backend, this.path, query);
- });
-
- test('it should make request with both params as strings', async function (assert) {
- assert.expect(1);
- const query = { version: '2', depth: '1' };
- this.server.get(this.subkeysUrl, (schema, { queryParams }) => {
- assert.propEqual(queryParams, query, `queryParams contain: ${JSON.stringify(queryParams)}`);
- return EXAMPLE_KV_SUBKEYS_RESPONSE;
- });
-
- this.adapter.fetchSubkeys(this.backend, this.path, query);
- });
-
- test('it should make request with both params as integers', async function (assert) {
- assert.expect(1);
- const query = { depth: 0, version: 2 };
- const expectedQuery = { depth: '0', version: '2' };
- this.server.get(this.subkeysUrl, (schema, { queryParams }) => {
- assert.propEqual(queryParams, expectedQuery, `queryParams contain: ${JSON.stringify(queryParams)}`);
- return EXAMPLE_KV_SUBKEYS_RESPONSE;
- });
-
- this.adapter.fetchSubkeys(this.backend, this.path, query);
- });
- });
-
- module('patchSecret', function (hooks) {
- hooks.beforeEach(function () {
- this.adapter = this.store.adapterFor('kv/data');
- });
-
- test('it should make request to patch', async function (assert) {
- assert.expect(2);
- const data = { foo: 'bar', baz: null };
- const expectedPayload = {
- data,
- options: {
- cas: 1,
- },
- };
- this.server.patch(this.endpoint('data'), (schema, req) => {
- const body = JSON.parse(req.requestBody);
- assert.true(true, `PATCH request made to ${this.endpoint('data')}`);
- assert.propEqual(body, expectedPayload, 'payload includes cas version');
- return EXAMPLE_KV_DATA_CREATE_RESPONSE;
- });
-
- this.adapter.patchSecret(this.backend, this.path, data, 1);
- });
- });
-});
diff --git a/ui/tests/unit/adapters/kv/metadata-test.js b/ui/tests/unit/adapters/kv/metadata-test.js
deleted file mode 100644
index 5145c0a498..0000000000
--- a/ui/tests/unit/adapters/kv/metadata-test.js
+++ /dev/null
@@ -1,189 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { kvMetadataPath } from 'vault/utils/kv-path';
-import { Response } from 'miragejs';
-
-const UTC_DATE = '1994-11-05T00:00:00.000Z';
-
-const EXAMPLE_KV_METADATA_GET_RESPONSE = {
- request_id: 'foobar',
- data: {
- cas_required: true,
- created_time: 'created-time',
- current_version: 2,
- custom_metadata: { application: 'staging' },
- delete_version_after: '0s',
- max_versions: 10,
- oldest_version: 0, // TODO: is this a bug? payload from real API
- updated_time: 'updated-time',
- versions: {
- 1: {
- created_time: 'created-time',
- deletion_time: UTC_DATE,
- destroyed: false,
- },
- 2: { created_time: 'created-time', deletion_time: '', destroyed: false },
- },
- },
-};
-
-module('Unit | Adapter | kv/metadata', function (hooks) {
- setupTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function () {
- this.store = this.owner.lookup('service:store');
- this.secretMountPath = this.owner.lookup('service:secret-mount-path');
- this.backend = 'some/kv-back&end';
- this.secretMountPath.currentPath = this.backend;
- this.path = 'beep/bop my/secret';
- this.id = kvMetadataPath(this.backend, this.path);
- this.data = {
- options: {
- cas: 2,
- },
- data: {
- foo: 'bar',
- },
- };
- this.payload = {
- max_versions: 2,
- cas_required: false,
- delete_version_after: '0s',
- custom_metadata: {
- admin: 'bob',
- },
- };
- this.endpoint = kvMetadataPath(this.backend, this.path);
- });
-
- test('it should make request to correct endpoint on createRecord', async function (assert) {
- assert.expect(10);
- const recordData = {
- backend: this.backend,
- path: this.path,
- deleteVersionAfter: '45h',
- customMetadata: { application: 'staging' },
- oldestVersion: 4,
- currentVersion: 6,
- createdTime: 'created',
- updatedTime: 'updated',
- versions: {
- 1: {
- created_time: 'created-time',
- deletion_time: UTC_DATE,
- destroyed: false,
- },
- 2: {
- created_time: 'created-time',
- deletion_time: '',
- destroyed: false,
- },
- },
- };
- const expectedBody = {
- max_versions: 0,
- delete_version_after: '45h',
- cas_required: false,
- custom_metadata: { application: 'staging' },
- };
- this.server.post(this.endpoint, (schema, req) => {
- const body = JSON.parse(req.requestBody);
- assert.ok('POST request made to correct endpoint when creating new record');
- assert.propEqual(body, expectedBody, 'POST request has correct body');
- return new Response(204);
- });
- const record = this.store.createRecord('kv/metadata', recordData);
- await record.save();
- assert.strictEqual(record.id, this.id, 'record has correct id');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.maxVersions, 0, 'record has default maxVersions');
- assert.false(record.casRequired, 'record has correct casRequired');
- assert.strictEqual(record.deleteVersionAfter, '45h', 'record has correct deleteVersionAfter');
- assert.deepEqual(record.customMetadata, { application: 'staging' }, 'record has correct customMetadata');
- assert.deepEqual(
- record.versions,
- EXAMPLE_KV_METADATA_GET_RESPONSE.data.versions,
- 'record has correct versions data'
- );
- });
-
- test('it should make request to correct endpoint on update record', async function (assert) {
- assert.expect(1);
- const data = this.server.create('kv-metadatum');
- data.id = kvMetadataPath('kv-engine', 'my-secret');
- this.store.pushPayload('kv/metadata', {
- modelName: 'kv/metadata',
- ...data,
- });
- this.server.post(kvMetadataPath('kv-engine', 'my-secret'), () => {
- assert.ok(true, 'request made to correct endpoint on delete metadata.');
- });
-
- const record = await this.store.peekRecord('kv/metadata', data.id);
- await record.save();
- });
-
- test('it should make request to correct endpoint on queryRecord', async function (assert) {
- assert.expect(13);
- this.server.get(this.endpoint, () => {
- assert.ok(true, 'request is made to correct url on queryRecord.');
- return EXAMPLE_KV_METADATA_GET_RESPONSE;
- });
-
- const record = await this.store.queryRecord('kv/metadata', { backend: this.backend, path: this.path });
- assert.strictEqual(record.id, this.id, 'record has correct id');
- assert.strictEqual(record.backend, this.backend, 'record has correct backend');
- assert.strictEqual(record.path, this.path, 'record has correct path');
- assert.strictEqual(record.maxVersions, 10, 'record has correct maxVersions');
- assert.true(record.casRequired, 'record has correct casRequired');
- assert.strictEqual(record.deleteVersionAfter, '0s', 'record has correct deleteVersionAfter');
- assert.deepEqual(record.customMetadata, { application: 'staging' }, 'record has correct customMetadata');
- assert.strictEqual(record.createdTime, 'created-time', 'record has correct createdTime');
- assert.strictEqual(record.currentVersion, 2, 'record has correct currentVersion');
- assert.strictEqual(record.oldestVersion, 0, 'record has correct oldestVersion');
- assert.strictEqual(record.updatedTime, 'updated-time', 'record has correct updatedTime');
- assert.deepEqual(
- record.versions,
- EXAMPLE_KV_METADATA_GET_RESPONSE.data.versions,
- 'record has correct versions data'
- );
- });
-
- test('it should make request to correct endpoint on query', async function (assert) {
- assert.expect(1);
- this.server.get(kvMetadataPath(this.backend, 'directory/'), (schema, req) => {
- assert.ok(req.queryParams.list, 'list query param sent when listing secrets');
- return { data: { keys: [] } };
- });
-
- this.store.query('kv/metadata', { backend: this.backend, pathToSecret: 'directory/' });
- });
-
- test('it should make request to correct endpoint on delete metadata', async function (assert) {
- assert.expect(3);
- const data = this.server.create('kv-metadatum');
- data.id = kvMetadataPath('kv-engine', 'my-secret');
- this.store.pushPayload('kv/metadata', {
- modelName: 'kv/metadata',
- ...data,
- });
- this.server.delete(kvMetadataPath('kv-engine', 'my-secret'), () => {
- assert.ok(true, 'request made to correct endpoint on delete metadata.');
- });
-
- let record = await this.store.peekRecord('kv/metadata', data.id);
-
- await record.destroyRecord();
- assert.true(record.isDeleted, 'record is deleted');
- record = await this.store.peekRecord('kv/metadata', this.id);
- assert.strictEqual(record, null, 'record is no longer in store');
- });
-});
diff --git a/ui/tests/unit/decorators/model-form-fields-test.js b/ui/tests/unit/decorators/model-form-fields-test.js
index 89080bbc34..cb4716e208 100644
--- a/ui/tests/unit/decorators/model-form-fields-test.js
+++ b/ui/tests/unit/decorators/model-form-fields-test.js
@@ -27,65 +27,116 @@ module('Unit | Decorators | ModelFormFields', function (hooks) {
assert.ok(this.spy.calledWith(message), 'Error is printed to console');
});
- test('it return allFields when arguments not provided', function (assert) {
+ test('it returns allFields when arguments not provided', function (assert) {
assert.expect(1);
// test by instantiating a record that uses this decorator
- const record = this.store.createRecord('kv/data');
+ const record = this.store.createRecord('totp-key');
assert.deepEqual(
record.allFields,
[
{
name: 'backend',
- options: {},
+ options: { readOnly: true },
type: 'string',
},
{
- name: 'path',
+ name: 'name',
options: {
- label: 'Path for this secret',
- subText: 'Names with forward slashes define hierarchical path structures.',
+ subText: 'Specifies the name for this key.',
},
type: 'string',
},
{
- name: 'secretData',
- options: {},
- type: 'object',
- },
- {
- name: 'createdTime',
- options: {},
+ name: 'accountName',
+ options: {
+ subText: 'The name of the account associated with the key. Required for keys generated by Vault.',
+ },
type: 'string',
},
{
- name: 'customMetadata',
- options: {},
- type: 'object',
- },
- {
- name: 'deletionTime',
- options: {},
+ name: 'algorithm',
+ options: { possibleValues: ['SHA1', 'SHA256', 'SHA512'], defaultValue: 'SHA1' },
type: 'string',
},
{
- name: 'destroyed',
- options: {},
+ name: 'digits',
+ options: { possibleValues: [6, 8], defaultValue: 6 },
+ type: 'number',
+ },
+ {
+ name: 'issuer',
+ options: {
+ subText: `The name of the key's issuing organization. Required for keys generated by Vault.`,
+ },
+ type: 'string',
+ },
+ {
+ name: 'period',
+ options: {
+ editType: 'ttl',
+ helperTextEnabled: 'How long each generated TOTP is valid.',
+ defaultValue: 30,
+ },
+ type: undefined,
+ },
+ {
+ name: 'generate',
+ options: {
+ label: 'Key Provider',
+ defaultValue: true,
+ editType: 'radio',
+ possibleValues: ['Vault', 'Other service'],
+ fieldValue: 'generateString',
+ subText: 'Specifies if the key should be generated by Vault or passed from another service.',
+ },
+ type: undefined,
+ },
+ {
+ name: 'keySize',
+ options: { defaultValue: 20 },
+ type: 'number',
+ },
+ {
+ name: 'skew',
+ options: { possibleValues: [0, 1], defaultValue: 1 },
+ type: 'number',
+ },
+ {
+ name: 'exported',
+ options: {
+ editType: 'toggleButton',
+ defaultValue: true,
+ helperTextDisabled: 'Vault will not return QR code and url upon key creation.',
+ helperTextEnabled: 'QR code and URL will be returned upon generating a key.',
+ },
type: 'boolean',
},
{
- name: 'version',
- options: {},
+ name: 'qrSize',
+ options: { label: 'QR size', defaultValue: 200 },
type: 'number',
},
{
- name: 'failReadErrorCode',
- options: {},
- type: 'number',
+ name: 'url',
+ options: {
+ label: 'URL',
+ helpText:
+ 'If a URL is provided the other fields can be left empty. E.g. otpauth://totp/Vault:test@test.com?secret=Y64VEVMBTSXCYIWRSHRNDZW62MPGVU2G&issuer=Vault',
+ subText: 'The TOTP key url string that can be used to configure a key.',
+ },
+ type: 'string',
},
{
- name: 'casVersion',
- options: {},
- type: 'number',
+ name: 'key',
+ options: {
+ subText: 'The root key used to generate a TOTP code.',
+ },
+ type: 'string',
+ },
+ {
+ name: 'barcode',
+ options: { readOnly: true },
+ type: 'string',
},
],
'allFields set on Model class'
diff --git a/ui/tests/unit/serializers/kv/data-test.js b/ui/tests/unit/serializers/kv/data-test.js
deleted file mode 100644
index 611d0d0c1f..0000000000
--- a/ui/tests/unit/serializers/kv/data-test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Serializer | kv/data', function (hooks) {
- setupTest(hooks);
-
- test('it should always pass the cas option when creating/updating a secret', function (assert) {
- const store = this.owner.lookup('service:store');
- const record = store.createRecord('kv/data', {
- path: 'my-secret-path',
- backend: 'kv-test',
- version: 2,
- casVersion: 3,
- secretData: { foo: 'bar' },
- });
- const expectedResult = {
- data: { foo: 'bar' },
- options: {
- cas: 3,
- },
- };
-
- const serializedRecord = record.serialize();
- assert.deepEqual(serializedRecord, expectedResult, 'cas option was correctly added to the payload.');
- });
-});
diff --git a/ui/tests/unit/serializers/kv/metadata-test.js b/ui/tests/unit/serializers/kv/metadata-test.js
deleted file mode 100644
index fa7116840a..0000000000
--- a/ui/tests/unit/serializers/kv/metadata-test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupTest } from 'ember-qunit';
-
-module('Unit | Serializer | kv/metadata', function (hooks) {
- setupTest(hooks);
-
- test('it should properly normalize a list response', function (assert) {
- const serializer = this.owner.lookup('serializer:kv/metadata');
- const serverData = {
- request_id: 'foo',
- backend: 'my-backend',
- path: '',
- data: {
- keys: ['first', 'second', 'third/'],
- },
- };
- const expectedData = [
- {
- id: 'my-backend/metadata/first',
- path: 'first',
- backend: 'my-backend',
- full_secret_path: 'first',
- },
- {
- id: 'my-backend/metadata/second',
- path: 'second',
- backend: 'my-backend',
- full_secret_path: 'second',
- },
- {
- id: 'my-backend/metadata/third/',
- path: 'third/',
- backend: 'my-backend',
- full_secret_path: 'third/',
- },
- ];
-
- const serializedRecord = serializer.normalizeItems(serverData);
- assert.deepEqual(serializedRecord, expectedData, 'transformed keys into proper IDs');
- });
-
- test('it should properly normalize a nested secret list response', function (assert) {
- const serializer = this.owner.lookup('serializer:kv/metadata');
- const serverData = {
- request_id: 'foo',
- backend: 'my-backend',
- path: 'beep/',
- data: {
- keys: ['boop/'],
- },
- };
- const expectedData = [
- {
- id: 'my-backend/metadata/beep/boop/',
- path: 'boop/',
- backend: 'my-backend',
- full_secret_path: 'beep/boop/',
- },
- ];
- const serializedRecord = serializer.normalizeItems(serverData);
- assert.deepEqual(serializedRecord, expectedData, 'transformed keys into proper IDs');
- });
-
- test('it throws an assertion if backend not on payload', function (assert) {
- const serializer = this.owner.lookup('serializer:kv/metadata');
- const serverData = {
- request_id: 'foo',
- data: {
- keys: ['first', 'second'],
- },
- };
- let result;
- try {
- result = serializer.normalizeItems(serverData);
- } catch (e) {
- result = e.message;
- }
- assert.strictEqual(
- result,
- 'Assertion Failed: payload.backend must be provided on kv/metadata list response'
- );
- });
-});
diff --git a/ui/tests/unit/services/path-help-test.js b/ui/tests/unit/services/path-help-test.js
index 1a5a2b273d..8050ff0abc 100644
--- a/ui/tests/unit/services/path-help-test.js
+++ b/ui/tests/unit/services/path-help-test.js
@@ -97,7 +97,7 @@ module('Unit | Service | path-help', function (hooks) {
assert.notOk(true, 'this method should not be called');
return reject();
});
- const modelType = 'kv/data';
+ const modelType = 'totp-key';
await this.pathHelp.getNewModel(modelType, 'my-kv').then(() => {
assert.true(true, 'getNewModel resolves');
});
diff --git a/ui/types/ember-data/types/registries/adapter.d.ts b/ui/types/ember-data/types/registries/adapter.d.ts
index e7d5cd7da7..033358f462 100644
--- a/ui/types/ember-data/types/registries/adapter.d.ts
+++ b/ui/types/ember-data/types/registries/adapter.d.ts
@@ -8,8 +8,6 @@ import Adapter from 'ember-data/adapter';
import ModelRegistry from 'ember-data/types/registries/model';
import ClientsActivityAdapter from 'vault/vault/adapters/clients/activity';
-import KvDataAdapter from 'vault/adapters/kv/data';
-import KvMetadataAdapter from 'vault/adapters/kv/metadata';
import LdapLibraryAdapter from 'vault/adapters/ldap/library';
import LdapRoleAdapter from 'vault/adapters/ldap/role';
import PkiIssuerAdapter from 'vault/adapters/pki/issuer';
@@ -26,8 +24,6 @@ export default interface AdapterRegistry {
'ldap/role': LdapRoleAdapter;
'pki/issuer': PkiIssuerAdapter;
'pki/tidy': PkiTidyAdapter;
- 'kv/data': KvDataAdapterAdapter;
- 'kv/metadata': KvMetadataAdapter;
'sync/destination': SyncDestinationAdapter;
'sync/association': SyncAssociationAdapter;
application: Application;
diff --git a/ui/types/ember-data/types/registries/model.d.ts b/ui/types/ember-data/types/registries/model.d.ts
index 39fd1c723f..347ef21adb 100644
--- a/ui/types/ember-data/types/registries/model.d.ts
+++ b/ui/types/ember-data/types/registries/model.d.ts
@@ -4,8 +4,6 @@
*/
import Model from '@ember-data/model';
-import KvSecretDataModel from 'vault/models/kv/data';
-import KvSecretMetadataModel from 'vault/models/kv/metadata';
import PkiActionModel from 'vault/models/pki/action';
import PkiCertificateGenerateModel from 'vault/models/pki/certificate/generate';
import PkiConfigAcmeModel from 'vault/models/pki/config/acme';
@@ -24,8 +22,6 @@ declare module 'ember-data/types/registries/model' {
'pki/config/cluster': PkiConfigClusterModel;
'pki/config/crl': PkiConfigCrlModel;
'pki/config/urls': PkiConfigUrlModel;
- 'kv/data': KvSecretDataModel;
- 'kv/metadata': KvSecretMetadataModel;
'clients/activity': ClientsActivityModel;
'clients/config': ClientsConfigModel;
'clients/version-history': ClientsVersionHistoryModel;