diff --git a/ui/app/adapters/secret-v2-version.js b/ui/app/adapters/secret-v2-version.js new file mode 100644 index 000000000..3927748bb --- /dev/null +++ b/ui/app/adapters/secret-v2-version.js @@ -0,0 +1,23 @@ +import { isEmpty } from '@ember/utils'; +import ApplicationAdapter from './application'; + +export default ApplicationAdapter.extend({ + namespace: 'v1', + _url(backend, id) { + let url = `${this.buildURL()}/${backend}/data/`; + if (!isEmpty(id)) { + url = url + id; + } + return url; + }, + + urlForFindRecord(id) { + let [backend, path, version] = JSON.parse(id); + return this._url(backend, path) + `?version=${version}`; + }, + + deleteRecord(store, type, snapshot) { + // use adapterOptions to determine if it's delete or destroy for the version + return this._super(...arguments); + }, +}); diff --git a/ui/app/adapters/secret-v2.js b/ui/app/adapters/secret-v2.js index d76fb2438..aa12c2169 100644 --- a/ui/app/adapters/secret-v2.js +++ b/ui/app/adapters/secret-v2.js @@ -1,34 +1,41 @@ +/* eslint-disable */ import { isEmpty } from '@ember/utils'; -import SecretAdapter from './secret'; +import ApplicationAdapter from './application'; -export default SecretAdapter.extend({ - createOrUpdate(store, type, snapshot) { - const serializer = store.serializerFor(type.modelName); - const data = serializer.serialize(snapshot); - const { id } = snapshot; - - return this.ajax(this.urlForSecret(snapshot.attr('backend'), id), 'POST', { - data: { data }, - }); - }, - - urlForSecret(backend, id, infix = 'data') { - let url = `${this.buildURL()}/${backend}/${infix}/`; +export default ApplicationAdapter.extend({ + namespace: 'v1', + _url(backend, id) { + let url = `${this.buildURL()}/${backend}/metadata/`; if (!isEmpty(id)) { url = url + id; } return url; }, - fetchByQuery(query, methodCall) { + // we override query here because the query object has a bunch of client-side + // concerns and we only want to send "list" to the server + query(store, type, query) { + let { backend, id } = query; + return this.ajax(this._url(backend, id), 'GET', { data: { list: true } }); + }, + + urlForQueryRecord(query) { let { id, backend } = query; - let args = [backend, id]; - if (methodCall === 'query') { - args.push('metadata'); - } - return this.ajax(this.urlForSecret(...args), 'GET', this.optionsForQuery(id, methodCall)).then(resp => { + return this._url(backend) + id; + }, + + queryRecord(store, type, query) { + let { backend, id } = query; + return this._super(...arguments).then(resp => { resp.id = id; + resp.backend = backend; return resp; }); }, + + urlForDeleteRecord(store, type, snapshot) { + let backend = snapshot.belongsTo('secret-engine', { id: true }); + let { id } = snapshot; + return this.urlForQueryRecord({ id, backend }); + }, }); diff --git a/ui/app/models/secret-v2-version.js b/ui/app/models/secret-v2-version.js new file mode 100644 index 000000000..3c7ca467a --- /dev/null +++ b/ui/app/models/secret-v2-version.js @@ -0,0 +1,8 @@ +import Secret from './secret'; +import DS from 'ember-data'; + +const { attr } = DS; + +export default Secret.extend({ + version: attr('number'), +}); diff --git a/ui/app/models/secret-v2.js b/ui/app/models/secret-v2.js index fcbf908ca..cfaef24fd 100644 --- a/ui/app/models/secret-v2.js +++ b/ui/app/models/secret-v2.js @@ -1,3 +1,15 @@ import Secret from './secret'; +import DS from 'ember-data'; -export default Secret.extend(); +const { attr, hasMany, belongsTo, Model } = DS; + +export default Model.extend({ + engine: belongsTo('secret-engine'), + versions: hasMany('secret-v2-version', { async: false }), + createdTime: attr(), + updatedTime: attr(), + currentVersion: attr('number'), + oldestVersion: attr('number'), + maxVersions: attr('number'), + casRequired: attr('boolean'), +}); 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 fb0cbb64a..67aec2bf6 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -72,7 +72,14 @@ export default Route.extend(UnloadModelRoute, { secret = secret.replace('cert/', ''); } return hash({ - secret: this.store.queryRecord(modelType, { id: secret, backend }), + secret: this.store.queryRecord(modelType, { id: secret, backend }).then(resp => { + if (modelType === 'secret-v2') { + // TODO, find by query param to enable viewing versions + let version = resp.versions.findBy('version', resp.currentVersion); + version.reload(); + } + return resp; + }), capabilities: this.capabilities(secret), }); }, diff --git a/ui/app/serializers/secret-v2-version.js b/ui/app/serializers/secret-v2-version.js new file mode 100644 index 000000000..31ffc9c90 --- /dev/null +++ b/ui/app/serializers/secret-v2-version.js @@ -0,0 +1,25 @@ +import { get } from '@ember/object'; +import ApplicationSerializer from './application'; + +export default ApplicationSerializer.extend({ + secretDataPath: 'data.data', + normalizeItems(payload, requestType) { + let path = this.secretDataPath; + // move response that is the contents of the secret from the dataPath + // to `secret_data` so it will be `secretData` in the model + payload.secret_data = get(payload, path); + payload = Object.assign({}, payload, payload.data.metadata); + delete payload.data; + // return the payload if it's expecting a single object or wrap + // it as an array if not + return payload; + }, + serialize(snapshot) { + return { + data: snapshot.attr('secretData'), + options: { + cas: snapshot.attr('currentVerion'), + }, + }; + }, +}); diff --git a/ui/app/serializers/secret-v2.js b/ui/app/serializers/secret-v2.js index 9a43c82b0..4dbbfd305 100644 --- a/ui/app/serializers/secret-v2.js +++ b/ui/app/serializers/secret-v2.js @@ -1,5 +1,40 @@ -import SecretSerializer from './secret'; +import ApplicationSerializer from './application'; +import DS from 'ember-data'; -export default SecretSerializer.extend({ - secretDataPath: 'data.data', +export default ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + versions: { embedded: 'always' }, + }, + secretDataPath: 'data', + normalizeItems(payload, requestType) { + if (payload.data.keys && Array.isArray(payload.data.keys)) { + // if we have data.keys, it's a list of ids, so we map over that + // and create objects with id's + return payload.data.keys.map(secret => { + // secrets don't have an id in the response, so we need to concat the full + // path of the secret here - the id in the payload is added + // in the adapter after making the request + let fullSecretPath = payload.id ? payload.id + secret : secret; + + // if there is no path, it's a "top level" secret, so add + // a unicode space for the id + // https://github.com/hashicorp/vault/issues/3348 + if (!fullSecretPath) { + fullSecretPath = '\u0020'; + } + return { id: fullSecretPath }; + }); + } + if (payload.data.versions) { + payload.data.versions = Object.keys(payload.data.versions).map(version => { + let body = payload.data.versions[version]; + body.version = version; + body.id = JSON.stringify([payload.backend, payload.id, version]); + return body; + }); + console.log(payload); + } + payload.data.id = payload.id; + return requestType === 'queryRecord' ? payload.data : [payload.data]; + }, });