From d4667de540384c3d2a00885273822c218faa6197 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Tue, 15 Jan 2019 11:55:12 -0500 Subject: [PATCH 01/84] ping openapi and grab current model --- ui/app/adapters/secret-engine.js | 14 +- ui/app/models/role-pki.js | 188 +++++++++--------- .../routes/vault/cluster/secrets/backend.js | 3 + .../cluster/secrets/backend/secret-edit.js | 23 ++- ui/app/services/path-help.js | 35 ++++ ui/app/utils/openapi-to-attrs.js | 20 ++ 6 files changed, 183 insertions(+), 100 deletions(-) create mode 100644 ui/app/services/path-help.js create mode 100644 ui/app/utils/openapi-to-attrs.js diff --git a/ui/app/adapters/secret-engine.js b/ui/app/adapters/secret-engine.js index 56481655368c..42b998f26905 100644 --- a/ui/app/adapters/secret-engine.js +++ b/ui/app/adapters/secret-engine.js @@ -7,16 +7,20 @@ export default ApplicationAdapter.extend({ return path ? url + '/' + path : url; }, + internalURL(path) { + let url = `/${this.urlPrefix()}/internal/ui/mounts`; + if (path) { + url = `${url}/${path}`; + } + return url; + }, + pathForType() { return 'mounts'; }, query(store, type, query) { - let url = `/${this.urlPrefix()}/internal/ui/mounts`; - if (query.path) { - url = `${url}/${query.path}`; - } - return this.ajax(url, 'GET'); + return this.ajax(this.internalURL(query.path), 'GET'); }, createRecord(store, type, snapshot) { diff --git a/ui/app/models/role-pki.js b/ui/app/models/role-pki.js index 77fe609d7df6..4181a1e221d9 100644 --- a/ui/app/models/role-pki.js +++ b/ui/app/models/role-pki.js @@ -15,100 +15,100 @@ export default DS.Model.extend({ fieldValue: 'id', readOnly: true, }), - keyType: attr('string', { - possibleValues: ['rsa', 'ec'], - }), - ttl: attr({ - label: 'TTL', - editType: 'ttl', - }), - maxTtl: attr({ - label: 'Max TTL', - editType: 'ttl', - }), - allowLocalhost: attr('boolean', {}), - allowedDomains: attr('string', {}), - allowBareDomains: attr('boolean', {}), - allowSubdomains: attr('boolean', {}), - allowGlobDomains: attr('boolean', {}), - allowAnyName: attr('boolean', {}), - enforceHostnames: attr('boolean', {}), - allowIpSans: attr('boolean', { - defaultValue: true, - label: 'Allow clients to request IP Subject Alternative Names (SANs)', - }), - allowedOtherSans: attr({ - editType: 'stringArray', - label: 'Allowed Other SANs', - }), - serverFlag: attr('boolean', { - defaultValue: true, - }), - clientFlag: attr('boolean', { - defaultValue: true, - }), - codeSigningFlag: attr('boolean', {}), - emailProtectionFlag: attr('boolean', {}), - keyBits: attr('number', { - defaultValue: 2048, - }), - keyUsage: attr('string', { - defaultValue: 'DigitalSignature,KeyAgreement,KeyEncipherment', - editType: 'stringArray', - }), - extKeyUsageOids: attr({ - label: 'Custom extended key usage OIDs', - editType: 'stringArray', - }), - requireCn: attr('boolean', { - label: 'Require common name', - defaultValue: true, - }), - useCsrCommonName: attr('boolean', { - label: 'Use CSR common name', - defaultValue: true, - }), - useCsrSans: attr('boolean', { - defaultValue: true, - label: 'Use CSR subject alternative names (SANs)', - }), - ou: attr({ - label: 'OU (OrganizationalUnit)', - editType: 'stringArray', - }), - organization: attr({ - editType: 'stringArray', - }), - country: attr({ - editType: 'stringArray', - }), - locality: attr({ - editType: 'stringArray', - label: 'Locality/City', - }), - province: attr({ - editType: 'stringArray', - label: 'Province/State', - }), - streetAddress: attr({ - editType: 'stringArray', - }), - postalCode: attr({ - editType: 'stringArray', - }), - generateLease: attr('boolean', {}), - noStore: attr('boolean', {}), - policyIdentifiers: attr({ - editType: 'stringArray', - }), - basicConstraintsValidForNonCA: attr('boolean', { - label: 'Mark Basic Constraints valid when issuing non-CA certificates.', - }), - notBeforeDuration: attr({ - label: 'Not Before Duration', - editType: 'ttl', - defaultValue: '30s', - }), + // keyType: attr('string', { + // possibleValues: ['rsa', 'ec'], + // }), + // ttl: attr({ + // label: 'TTL', + // editType: 'ttl', + // }), + // maxTtl: attr({ + // label: 'Max TTL', + // editType: 'ttl', + // }), + // allowLocalhost: attr('boolean', {}), + // allowedDomains: attr('string', {}), + // allowBareDomains: attr('boolean', {}), + // allowSubdomains: attr('boolean', {}), + // allowGlobDomains: attr('boolean', {}), + // allowAnyName: attr('boolean', {}), + // enforceHostnames: attr('boolean', {}), + // allowIpSans: attr('boolean', { + // defaultValue: true, + // label: 'Allow clients to request IP Subject Alternative Names (SANs)', + // }), + // allowedOtherSans: attr({ + // editType: 'stringArray', + // label: 'Allowed Other SANs', + // }), + // serverFlag: attr('boolean', { + // defaultValue: true, + // }), + // clientFlag: attr('boolean', { + // defaultValue: true, + // }), + // codeSigningFlag: attr('boolean', {}), + // emailProtectionFlag: attr('boolean', {}), + // keyBits: attr('number', { + // defaultValue: 2048, + // }), + // keyUsage: attr('string', { + // defaultValue: 'DigitalSignature,KeyAgreement,KeyEncipherment', + // editType: 'stringArray', + // }), + // extKeyUsageOids: attr({ + // label: 'Custom extended key usage OIDs', + // editType: 'stringArray', + // }), + // requireCn: attr('boolean', { + // label: 'Require common name', + // defaultValue: true, + // }), + // useCsrCommonName: attr('boolean', { + // label: 'Use CSR common name', + // defaultValue: true, + // }), + // useCsrSans: attr('boolean', { + // defaultValue: true, + // label: 'Use CSR subject alternative names (SANs)', + // }), + // ou: attr({ + // label: 'OU (OrganizationalUnit)', + // editType: 'stringArray', + // }), + // organization: attr({ + // editType: 'stringArray', + // }), + // country: attr({ + // editType: 'stringArray', + // }), + // locality: attr({ + // editType: 'stringArray', + // label: 'Locality/City', + // }), + // province: attr({ + // editType: 'stringArray', + // label: 'Province/State', + // }), + // streetAddress: attr({ + // editType: 'stringArray', + // }), + // postalCode: attr({ + // editType: 'stringArray', + // }), + // generateLease: attr('boolean', {}), + // noStore: attr('boolean', {}), + // policyIdentifiers: attr({ + // editType: 'stringArray', + // }), + // basicConstraintsValidForNonCA: attr('boolean', { + // label: 'Mark Basic Constraints valid when issuing non-CA certificates.', + // }), + // notBeforeDuration: attr({ + // label: 'Not Before Duration', + // editType: 'ttl', + // defaultValue: '30s', + // }), updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'), canDelete: alias('updatePath.canDelete'), diff --git a/ui/app/routes/vault/cluster/secrets/backend.js b/ui/app/routes/vault/cluster/secrets/backend.js index b7964a59b186..70bd9f2abe45 100644 --- a/ui/app/routes/vault/cluster/secrets/backend.js +++ b/ui/app/routes/vault/cluster/secrets/backend.js @@ -1,7 +1,10 @@ import { inject as service } from '@ember/service'; +import { getOwner } from '@ember/application'; +import { run } from '@ember/runloop'; import Route from '@ember/routing/route'; export default Route.extend({ flashMessages: service(), + oldModel: null, model(params) { let { backend } = params; return this.store 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 943ad2a54a0a..1e64b38b3f0e 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -1,11 +1,13 @@ import { set } from '@ember/object'; import { hash, resolve } from 'rsvp'; +import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import utils from 'vault/lib/key-utils'; +import { getOwner } from '@ember/application'; import UnloadModelRoute from 'vault/mixins/unload-model-route'; -import DS from 'ember-data'; export default Route.extend(UnloadModelRoute, { + pathHelp: service('path-help'), capabilities(secret) { const { backend } = this.paramsFor('vault.cluster.secrets.backend'); let backendModel = this.modelFor('vault.cluster.secrets.backend'); @@ -35,6 +37,7 @@ export default Route.extend(UnloadModelRoute, { // perhaps in the future we could recurse _for_ users, but for now, just kick them // back to the list const { secret } = this.paramsFor(this.routeName); + this.buildModel(secret); const parentKey = utils.parentKeyForKey(secret); const mode = this.routeName.split('.').pop(); if (mode === 'edit' && utils.keyIsFolder(secret)) { @@ -46,6 +49,24 @@ export default Route.extend(UnloadModelRoute, { } }, + buildModel(secret) { + const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let modelType = this.modelType(backend, secret); + let modelFactory = getOwner(this).factoryFor(`model:${modelType}`); + this.pathHelp.getAttrs(modelType, backend).then(attrs => { + let newModel; + if (modelFactory) { + //combine them + newModel = modelFactory.class.reopen({ + attrs, + }); + } else { + //generate a whole new model + } + getOwner(this).register(newModel.toString(), newModel); + }); + }, + modelType(backend, secret) { let backendModel = this.modelFor('vault.cluster.secrets.backend', backend); let type = backendModel.get('engineType'); diff --git a/ui/app/services/path-help.js b/ui/app/services/path-help.js new file mode 100644 index 000000000000..e70f4d3cbe51 --- /dev/null +++ b/ui/app/services/path-help.js @@ -0,0 +1,35 @@ +// Low level service that allows users to input paths to make requests to vault +// this service provides the UI synecdote to the cli commands read, write, delete, and list +import Service from '@ember/service'; + +import { getOwner } from '@ember/application'; +import { expandOpenApiProps } from 'vault/utils/openapi-to-attrs'; + +export function sanitizePath(path) { + //remove whitespace + remove trailing and leading slashes + return path.trim().replace(/^\/+|\/+$/g, ''); +} + +export default Service.extend({ + attrs: null, + ajax(url, options = {}) { + let appAdapter = getOwner(this).lookup(`adapter:application`); + let { data } = options; + return appAdapter.ajax(url, 'GET', { + data, + }); + }, + + getAttrs(modelType, backend) { + let adapter = getOwner(this).lookup(`adapter:${modelType}`); + let path = adapter.pathForType(); + let helpUrl = `/v1/${backend}/${path}/example?help=1`; + let wildcard = { roles: 'name', mounts: 'config' }[path]; + return this.ajax(helpUrl, backend).then(help => { + let props = + help.openapi.paths[`/${path}/{${wildcard}}`].post.requestBody.content['application/json'].schema + .properties; + return expandOpenApiProps(props); + }); + }, +}); diff --git a/ui/app/utils/openapi-to-attrs.js b/ui/app/utils/openapi-to-attrs.js new file mode 100644 index 000000000000..c440450868b6 --- /dev/null +++ b/ui/app/utils/openapi-to-attrs.js @@ -0,0 +1,20 @@ +import DS from 'ember-data'; +const { attr } = DS; + +export const expandOpenApiProps = function(props) { + let attrs = {}; + // expand all attributes + for (let prop in props) { + let details = props[prop]; + let editType = details.type; + if (details.format === 'seconds') { + editType = 'ttl'; + } else if (details.items) { + editType = details.items.type + details.type.capitalize(); + } + attrs[prop.camelize()] = attr({ + editType: editType || details.type, + }); + } + return attrs; +}; From 0faf738a1fc7856523f702bfb804a95d9b999556 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Wed, 16 Jan 2019 11:32:43 -0500 Subject: [PATCH 02/84] add debugger statements to figure out order of operations --- ui/app/models/role-pki.js | 8 ++++---- ui/app/routes/vault/cluster/secrets/backend.js | 3 +++ .../vault/cluster/secrets/backend/secret-edit.js | 13 ++++++++----- ui/app/utils/field-to-attrs.js | 2 ++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ui/app/models/role-pki.js b/ui/app/models/role-pki.js index 4181a1e221d9..91fa112c67df 100644 --- a/ui/app/models/role-pki.js +++ b/ui/app/models/role-pki.js @@ -15,9 +15,9 @@ export default DS.Model.extend({ fieldValue: 'id', readOnly: true, }), - // keyType: attr('string', { - // possibleValues: ['rsa', 'ec'], - // }), + keyType: attr('string', { + possibleValues: ['rsa', 'ec'], + }), // ttl: attr({ // label: 'TTL', // editType: 'ttl', @@ -124,7 +124,7 @@ export default DS.Model.extend({ signVerbatimPath: lazyCapabilities(apiPath`${'backend'}/sign-verbatim/${'id'}`, 'backend', 'id'), canSignVerbatim: alias('signVerbatimPath.canUpdate'), - fieldGroups: computed(function() { + fieldGroups: computed('backend', function() { const groups = [ { default: ['name', 'keyType'] }, { diff --git a/ui/app/routes/vault/cluster/secrets/backend.js b/ui/app/routes/vault/cluster/secrets/backend.js index 70bd9f2abe45..060dbbc9631c 100644 --- a/ui/app/routes/vault/cluster/secrets/backend.js +++ b/ui/app/routes/vault/cluster/secrets/backend.js @@ -5,6 +5,9 @@ import Route from '@ember/routing/route'; export default Route.extend({ flashMessages: service(), oldModel: null, + beforeModel(params) { + debugger; //eslint-disable-line + }, model(params) { let { backend } = params; return this.store 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 1e64b38b3f0e..7fde1a0afe8a 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -32,7 +32,7 @@ export default Route.extend(UnloadModelRoute, { templateName: 'vault/cluster/secrets/backend/secretEditLayout', - beforeModel() { + beforeModel(params) { // currently there is no recursive delete for folders in vault, so there's no need to 'edit folders' // perhaps in the future we could recurse _for_ users, but for now, just kick them // back to the list @@ -53,17 +53,20 @@ export default Route.extend(UnloadModelRoute, { const { backend } = this.paramsFor('vault.cluster.secrets.backend'); let modelType = this.modelType(backend, secret); let modelFactory = getOwner(this).factoryFor(`model:${modelType}`); + let owner = getOwner(this); this.pathHelp.getAttrs(modelType, backend).then(attrs => { let newModel; if (modelFactory) { //combine them - newModel = modelFactory.class.reopen({ - attrs, - }); + newModel = modelFactory.class.reopenClass(attrs); } else { //generate a whole new model } - getOwner(this).register(newModel.toString(), newModel); + newModel = newModel.class.reopenClass({ merged: true }); + debugger; //eslint-disable-line + owner.unregister(newModel.toString()); + owner.register(newModel.toString(), newModel); + this.refresh(); }); }, diff --git a/ui/app/utils/field-to-attrs.js b/ui/app/utils/field-to-attrs.js index 730c9076bc19..e6c5f0a0b45a 100644 --- a/ui/app/utils/field-to-attrs.js +++ b/ui/app/utils/field-to-attrs.js @@ -36,6 +36,7 @@ import { expandProperties } from '@ember/object/computed'; */ export const expandAttributeMeta = function(modelClass, attributeNames, namePrefix, map) { + debugger; //eslint-disable-line let fields = []; // expand all attributes attributeNames.forEach(field => expandProperties(field, x => fields.push(x))); @@ -96,6 +97,7 @@ export const expandAttributeMeta = function(modelClass, attributeNames, namePref */ export default function(modelClass, fieldGroups) { + debugger; //eslint-disable-line return fieldGroups.map(group => { const groupKey = Object.keys(group)[0]; const fields = expandAttributeMeta(modelClass, group[groupKey]); From 64962a39db6f2fca0112a924b1f24bb72a9cf10f Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 18 Jan 2019 17:41:34 -0600 Subject: [PATCH 03/84] block in model generation strategy --- .../cluster/secrets/backend/secret-edit.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 7fde1a0afe8a..66df6be7f590 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -37,36 +37,36 @@ export default Route.extend(UnloadModelRoute, { // perhaps in the future we could recurse _for_ users, but for now, just kick them // back to the list const { secret } = this.paramsFor(this.routeName); - this.buildModel(secret); - const parentKey = utils.parentKeyForKey(secret); - const mode = this.routeName.split('.').pop(); - if (mode === 'edit' && utils.keyIsFolder(secret)) { - if (parentKey) { - return this.transitionTo('vault.cluster.secrets.backend.list', parentKey); - } else { - return this.transitionTo('vault.cluster.secrets.backend.list-root'); + return this.buildModel(secret).then(() => { + const parentKey = utils.parentKeyForKey(secret); + const mode = this.routeName.split('.').pop(); + if (mode === 'edit' && utils.keyIsFolder(secret)) { + if (parentKey) { + return this.transitionTo('vault.cluster.secrets.backend.list', parentKey); + } else { + return this.transitionTo('vault.cluster.secrets.backend.list-root'); + } } - } + }); }, buildModel(secret) { const { backend } = this.paramsFor('vault.cluster.secrets.backend'); let modelType = this.modelType(backend, secret); - let modelFactory = getOwner(this).factoryFor(`model:${modelType}`); + let name = `model:${modelType}`; let owner = getOwner(this); - this.pathHelp.getAttrs(modelType, backend).then(attrs => { - let newModel; - if (modelFactory) { + return this.pathHelp.getAttrs(modelType, backend).then(attrs => { + let newModel = owner.factoryFor(name).class; + if (owner.hasRegistration(name) && !newModel.merged) { //combine them - newModel = modelFactory.class.reopenClass(attrs); + // TODO - this doesn't merge attributes + newModel = newModel.extend(attrs); } else { //generate a whole new model } - newModel = newModel.class.reopenClass({ merged: true }); - debugger; //eslint-disable-line - owner.unregister(newModel.toString()); - owner.register(newModel.toString(), newModel); - this.refresh(); + newModel.reopenClass({ merged: true }); + owner.unregister(name); + owner.register(name, newModel); }); }, From f150cee2463a67bb41f285259fa84ffca86a3954 Mon Sep 17 00:00:00 2001 From: Madalyn Parker Date: Wed, 23 Jan 2019 12:07:05 -0500 Subject: [PATCH 04/84] make sure all form fields appear correctly for pki --- ui/app/models/role-pki.js | 2 +- .../cluster/secrets/backend/secret-edit.js | 6 +++-- ui/app/templates/components/form-field.hbs | 15 ++++++++--- ui/app/utils/field-to-attrs.js | 3 +-- ui/app/utils/openapi-to-attrs.js | 27 ++++++++++++++++--- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/ui/app/models/role-pki.js b/ui/app/models/role-pki.js index 91fa112c67df..fa018c69ebab 100644 --- a/ui/app/models/role-pki.js +++ b/ui/app/models/role-pki.js @@ -167,7 +167,7 @@ export default DS.Model.extend({ ], }, { - Advanced: ['generateLease', 'noStore', 'basicConstraintsValidForNonCA', 'policyIdentifiers'], + Advanced: ['generateLease', 'noStore', 'basicConstraintsValidForNonCa', 'policyIdentifiers'], }, ]; 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 66df6be7f590..a2cd340f949c 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -5,6 +5,7 @@ import Route from '@ember/routing/route'; import utils from 'vault/lib/key-utils'; import { getOwner } from '@ember/application'; import UnloadModelRoute from 'vault/mixins/unload-model-route'; +import { combineAttributes } from 'vault/utils/openapi-to-attrs'; export default Route.extend(UnloadModelRoute, { pathHelp: service('path-help'), @@ -55,11 +56,12 @@ export default Route.extend(UnloadModelRoute, { let modelType = this.modelType(backend, secret); let name = `model:${modelType}`; let owner = getOwner(this); - return this.pathHelp.getAttrs(modelType, backend).then(attrs => { + return this.pathHelp.getProps(modelType, backend).then(props => { let newModel = owner.factoryFor(name).class; if (owner.hasRegistration(name) && !newModel.merged) { //combine them - // TODO - this doesn't merge attributes + let attrs = combineAttributes(newModel.attributes, props); + debugger; //eslint-disable-line newModel = newModel.extend(attrs); } else { //generate a whole new model diff --git a/ui/app/templates/components/form-field.hbs b/ui/app/templates/components/form-field.hbs index c84e51cb77e6..91fd3a89d162 100644 --- a/ui/app/templates/components/form-field.hbs +++ b/ui/app/templates/components/form-field.hbs @@ -1,7 +1,12 @@ {{#unless (or - (and attr.options.editType (not-eq attr.options.editType "textarea")) (eq attr.type "boolean") + (contains + attr.options.editType + (array + "searchSelect" "mountAccessor" "kv" "file" "ttl" "stringArray" "json" + ) + ) ) }} {{/unless}} {{#if attr.options.possibleValues}} + {{log attr}}