From 405484df78c38a511153b18960f1637001d84b03 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 09:51:56 -0600 Subject: [PATCH 01/19] directly depend on route-recognizer --- ui/package.json | 1 + ui/yarn.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index 7cfa5c23d75e..d3c34a60ba67 100644 --- a/ui/package.json +++ b/ui/package.json @@ -106,6 +106,7 @@ "prettier": "^1.14.3", "prettier-eslint-cli": "^4.7.1", "qunit-dom": "^0.7.1", + "route-recognizer": "^0.3.4", "sass-svg-uri": "^1.0.0", "string.prototype.endswith": "^0.2.0", "string.prototype.startswith": "^0.2.0", diff --git a/ui/yarn.lock b/ui/yarn.lock index 3839ea2cc29b..28bcd8bda8ed 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -11452,7 +11452,7 @@ rollup@^0.57.1: signal-exit "^3.0.2" sourcemap-codec "^1.4.1" -route-recognizer@^0.3.3: +route-recognizer@^0.3.3, route-recognizer@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3" integrity sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g== From 3f4120ec00d34bcbae772b453013b0602d4dcae2 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 10:46:22 -0600 Subject: [PATCH 02/19] add path encode helper using route-recognizer normalizer methods --- ui/app/utils/path-encoding-helpers.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ui/app/utils/path-encoding-helpers.js diff --git a/ui/app/utils/path-encoding-helpers.js b/ui/app/utils/path-encoding-helpers.js new file mode 100644 index 000000000000..88b254a1a3b9 --- /dev/null +++ b/ui/app/utils/path-encoding-helpers.js @@ -0,0 +1,14 @@ +import RouteRecognizer from 'route-recognizer'; + +const { + Normalizer: { normalizePath, encodePathSegment }, +} = RouteRecognizer; + +export function encodePath(path) { + return path + .split('/') + .map(encodePathSegment) + .join('/'); +} + +export { normalizePath, encodePathSegment }; From 42f0bcdaf962df9277a16282a2a4fd27613bb8ee Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 10:46:58 -0600 Subject: [PATCH 03/19] encode user-entered paths/ids for places we're not using the built-in ember data buildUrl method --- ui/app/adapters/auth-method.js | 5 +++-- ui/app/adapters/lease.js | 7 ++++--- ui/app/adapters/role-aws.js | 5 +++-- ui/app/adapters/role-jwt.js | 3 ++- ui/app/adapters/role-pki.js | 5 +++-- ui/app/adapters/role-ssh.js | 7 ++++--- ui/app/adapters/secret-engine.js | 17 +++++++++-------- ui/app/adapters/secret-v2.js | 5 +++-- ui/app/adapters/secret.js | 5 +++-- ui/app/adapters/transit-key.js | 13 +++++++------ 10 files changed, 41 insertions(+), 31 deletions(-) diff --git a/ui/app/adapters/auth-method.js b/ui/app/adapters/auth-method.js index 3691cc35831f..38f79aa11db2 100644 --- a/ui/app/adapters/auth-method.js +++ b/ui/app/adapters/auth-method.js @@ -2,11 +2,12 @@ import { assign } from '@ember/polyfills'; import { get, set } from '@ember/object'; import ApplicationAdapter from './application'; import DS from 'ember-data'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ url(path) { const url = `${this.buildURL()}/auth`; - return path ? url + '/' + path : url; + return path ? url + '/' + encodePath(path) : url; }, // used in updateRecord on the model#tune action @@ -58,6 +59,6 @@ export default ApplicationAdapter.extend({ }, exchangeOIDC(path, state, code) { - return this.ajax(`/v1/auth/${path}/oidc/callback`, 'GET', { data: { state, code } }); + return this.ajax(`/v1/auth/${encodePath(path)}/oidc/callback`, 'GET', { data: { state, code } }); }, }); diff --git a/ui/app/adapters/lease.js b/ui/app/adapters/lease.js index 4b2044159cc4..67fff5e2da4a 100644 --- a/ui/app/adapters/lease.js +++ b/ui/app/adapters/lease.js @@ -1,14 +1,15 @@ import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ revokePrefix(prefix) { - let url = this.buildURL() + '/leases/revoke-prefix/' + prefix; + let url = this.buildURL() + '/leases/revoke-prefix/' + encodePath(prefix); url = url.replace(/\/$/, ''); return this.ajax(url, 'PUT'); }, forceRevokePrefix(prefix) { - let url = this.buildURL() + '/leases/revoke-prefix/' + prefix; + let url = this.buildURL() + '/leases/revoke-prefix/' + encodePath(prefix); url = url.replace(/\/$/, ''); return this.ajax(url, 'PUT'); }, @@ -43,7 +44,7 @@ export default ApplicationAdapter.extend({ query(store, type, query) { const prefix = query.prefix || ''; - return this.ajax(this.buildURL() + '/leases/lookup/' + prefix, 'GET', { + return this.ajax(this.buildURL() + '/leases/lookup/' + encodePath(prefix), 'GET', { data: { list: true, }, diff --git a/ui/app/adapters/role-aws.js b/ui/app/adapters/role-aws.js index cd3b330bf729..da9b818e1732 100644 --- a/ui/app/adapters/role-aws.js +++ b/ui/app/adapters/role-aws.js @@ -1,5 +1,6 @@ import { assign } from '@ember/polyfills'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', @@ -31,9 +32,9 @@ export default ApplicationAdapter.extend({ }, urlForRole(backend, id) { - let url = `${this.buildURL()}/${backend}/roles`; + let url = `${this.buildURL()}/${encodePath(backend)}/roles`; if (id) { - url = url + '/' + id; + url = url + '/' + encodePath(id); } return url; }, diff --git a/ui/app/adapters/role-jwt.js b/ui/app/adapters/role-jwt.js index 029b6c5925b0..d335cc3321af 100644 --- a/ui/app/adapters/role-jwt.js +++ b/ui/app/adapters/role-jwt.js @@ -1,6 +1,7 @@ import ApplicationAdapter from './application'; import { inject as service } from '@ember/service'; import { get } from '@ember/object'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ router: service(), @@ -9,7 +10,7 @@ export default ApplicationAdapter.extend({ let [path, role] = JSON.parse(id); let namespace = get(snapshot, 'adapterOptions.namespace'); - let url = `/v1/auth/${path}/oidc/auth_url`; + let url = `/v1/auth/${encodePath(path)}/oidc/auth_url`; let redirect_uri = `${window.location.origin}${this.router.urlFor('vault.cluster.oidc-callback', { auth_path: path, })}`; diff --git a/ui/app/adapters/role-pki.js b/ui/app/adapters/role-pki.js index cd3b330bf729..da9b818e1732 100644 --- a/ui/app/adapters/role-pki.js +++ b/ui/app/adapters/role-pki.js @@ -1,5 +1,6 @@ import { assign } from '@ember/polyfills'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', @@ -31,9 +32,9 @@ export default ApplicationAdapter.extend({ }, urlForRole(backend, id) { - let url = `${this.buildURL()}/${backend}/roles`; + let url = `${this.buildURL()}/${encodePath(backend)}/roles`; if (id) { - url = url + '/' + id; + url = url + '/' + encodePath(id); } return url; }, diff --git a/ui/app/adapters/role-ssh.js b/ui/app/adapters/role-ssh.js index c41c3f40b49f..b0390be528e9 100644 --- a/ui/app/adapters/role-ssh.js +++ b/ui/app/adapters/role-ssh.js @@ -1,6 +1,7 @@ import { assign } from '@ember/polyfills'; import { resolve, allSettled } from 'rsvp'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', @@ -34,9 +35,9 @@ export default ApplicationAdapter.extend({ }, urlForRole(backend, id) { - let url = `${this.buildURL()}/${backend}/roles`; + let url = `${this.buildURL()}/${encodePath(backend)}/roles`; if (id) { - url = url + '/' + id; + url = url + '/' + encodePath(id); } return url; }, @@ -84,7 +85,7 @@ export default ApplicationAdapter.extend({ findAllZeroAddress(store, query) { const { backend } = query; - const url = `/v1/${backend}/config/zeroaddress`; + const url = `/v1/${encodePath(backend)}/config/zeroaddress`; return this.ajax(url, 'GET'); }, diff --git a/ui/app/adapters/secret-engine.js b/ui/app/adapters/secret-engine.js index 42b998f26905..21f926a4587d 100644 --- a/ui/app/adapters/secret-engine.js +++ b/ui/app/adapters/secret-engine.js @@ -1,16 +1,17 @@ import { assign } from '@ember/polyfills'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ url(path) { const url = `${this.buildURL()}/mounts`; - return path ? url + '/' + path : url; + return path ? url + '/' + encodePath(path) : url; }, internalURL(path) { let url = `/${this.urlPrefix()}/internal/ui/mounts`; if (path) { - url = `${url}/${path}`; + url = `${url}/${encodePath(path)}`; } return url; }, @@ -38,14 +39,14 @@ export default ApplicationAdapter.extend({ findRecord(store, type, path, snapshot) { if (snapshot.attr('type') === 'ssh') { - return this.ajax(`/v1/${path}/config/ca`, 'GET'); + return this.ajax(`/v1/${encodePath(path)}/config/ca`, 'GET'); } return; }, queryRecord(store, type, query) { if (query.type === 'aws') { - return this.ajax(`/v1/${query.backend}/config/lease`, 'GET').then(resp => { + return this.ajax(`/v1/${encodePath(query.backend)}/config/lease`, 'GET').then(resp => { resp.path = query.backend + '/'; return resp; }); @@ -61,25 +62,25 @@ export default ApplicationAdapter.extend({ if (apiPath) { const serializer = store.serializerFor(type.modelName); const data = serializer.serialize(snapshot); - const path = snapshot.id; + const path = encodePath(snapshot.id); return this.ajax(`/v1/${path}/${apiPath}`, options.isDelete ? 'DELETE' : 'POST', { data }); } }, saveAWSRoot(store, type, snapshot) { let { data } = snapshot.adapterOptions; - const path = snapshot.id; + const path = encodePath(snapshot.id); return this.ajax(`/v1/${path}/config/root`, 'POST', { data }); }, saveAWSLease(store, type, snapshot) { let { data } = snapshot.adapterOptions; - const path = snapshot.id; + const path = encodePath(snapshot.id); return this.ajax(`/v1/${path}/config/lease`, 'POST', { data }); }, saveZeroAddressConfig(store, type, snapshot) { - const path = snapshot.id; + const path = encodePath(snapshot.id); const roles = store .peekAll('role-ssh') .filterBy('zeroAddress') diff --git a/ui/app/adapters/secret-v2.js b/ui/app/adapters/secret-v2.js index 14310c070d38..517f83d77d36 100644 --- a/ui/app/adapters/secret-v2.js +++ b/ui/app/adapters/secret-v2.js @@ -1,13 +1,14 @@ /* eslint-disable */ import { isEmpty } from '@ember/utils'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', _url(backend, id) { - let url = `${this.buildURL()}/${backend}/metadata/`; + let url = `${this.buildURL()}/${encodePath(backend)}/metadata/`; if (!isEmpty(id)) { - url = url + id; + url = url + encodePath(id); } return url; }, diff --git a/ui/app/adapters/secret.js b/ui/app/adapters/secret.js index 59c39a0387ea..3d5fc059cf13 100644 --- a/ui/app/adapters/secret.js +++ b/ui/app/adapters/secret.js @@ -1,5 +1,6 @@ import { isEmpty } from '@ember/utils'; import ApplicationAdapter from './application'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', @@ -26,9 +27,9 @@ export default ApplicationAdapter.extend({ }, urlForSecret(backend, id) { - let url = `${this.buildURL()}/${backend}/`; + let url = `${this.buildURL()}/${encodePath(backend)}/`; if (!isEmpty(id)) { - url = url + id; + url = url + encodePath(id); } return url; diff --git a/ui/app/adapters/transit-key.js b/ui/app/adapters/transit-key.js index 876c0096a87d..9e23280be00b 100644 --- a/ui/app/adapters/transit-key.js +++ b/ui/app/adapters/transit-key.js @@ -1,5 +1,6 @@ import ApplicationAdapter from './application'; import { pluralize } from 'ember-inflector'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', @@ -47,29 +48,29 @@ export default ApplicationAdapter.extend({ }, urlForSecret(backend, id) { - let url = `${this.buildURL()}/${backend}/keys/`; + let url = `${this.buildURL()}/${encodePath(backend)}/keys/`; if (id) { - url += id; + url += encodePath(id); } return url; }, urlForAction(action, backend, id, param) { - let urlBase = `${this.buildURL()}/${backend}/${action}`; + let urlBase = `${this.buildURL()}/${encodePath(backend)}/${action}`; // these aren't key-specific if (action === 'hash' || action === 'random') { return urlBase; } if (action === 'datakey' && param) { // datakey action has `wrapped` or `plaintext` as part of the url - return `${urlBase}/${param}/${id}`; + return `${urlBase}/${param}/${encodePath(id)}`; } if (action === 'export' && param) { let [type, version] = param; - const exportBase = `${urlBase}/${type}-key/${id}`; + const exportBase = `${urlBase}/${type}-key/${encodePath(id)}`; return version ? `${exportBase}/${version}` : exportBase; } - return `${urlBase}/${id}`; + return `${urlBase}/${encodePath(id)}`; }, optionsForQuery(id) { From 0dfd63f45e095a5d6c865c730943b8df770623ba Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 11:00:52 -0600 Subject: [PATCH 04/19] encode secret link params --- ui/app/components/secret-link.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/secret-link.js b/ui/app/components/secret-link.js index e563a5900390..0b2b12d0aaa5 100644 --- a/ui/app/components/secret-link.js +++ b/ui/app/components/secret-link.js @@ -1,5 +1,6 @@ import { computed } from '@ember/object'; import Component from '@ember/component'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export function linkParams({ mode, secret, queryParams }) { let params; @@ -8,7 +9,7 @@ export function linkParams({ mode, secret, queryParams }) { if (!secret || secret === ' ') { params = [route + '-root']; } else { - params = [route, secret]; + params = [route, encodePath(secret)]; } if (queryParams) { From f50ef8a741463611c5e66c8712be8e6491a1c565 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 14:20:51 -0600 Subject: [PATCH 05/19] decode params from the url, and encode for linked-block and navigate-input components --- ui/app/adapters/secret-v2-version.js | 5 +-- ui/app/components/key-value-header.js | 3 +- ui/app/components/linked-block.js | 9 +++++- ui/app/components/navigate-input.js | 12 +++++-- .../vault/cluster/secrets/backend/list.js | 31 ++++++++++++------ .../cluster/secrets/backend/secret-edit.js | 32 ++++++++++++------- 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/ui/app/adapters/secret-v2-version.js b/ui/app/adapters/secret-v2-version.js index 1c9236470a9c..1e2177dc05cf 100644 --- a/ui/app/adapters/secret-v2-version.js +++ b/ui/app/adapters/secret-v2-version.js @@ -3,13 +3,14 @@ import { isEmpty } from '@ember/utils'; import { get } from '@ember/object'; import ApplicationAdapter from './application'; import DS from 'ember-data'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default ApplicationAdapter.extend({ namespace: 'v1', _url(backend, id, infix = 'data') { - let url = `${this.buildURL()}/${backend}/${infix}/`; + let url = `${this.buildURL()}/${encodePath(backend)}/${infix}/`; if (!isEmpty(id)) { - url = url + id; + url = url + encodePath(id); } return url; }, diff --git a/ui/app/components/key-value-header.js b/ui/app/components/key-value-header.js index 24498e99a935..7e9b59b4cc1f 100644 --- a/ui/app/components/key-value-header.js +++ b/ui/app/components/key-value-header.js @@ -1,6 +1,7 @@ import { computed } from '@ember/object'; import Component from '@ember/component'; import utils from 'vault/lib/key-utils'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export default Component.extend({ tagName: 'nav', @@ -31,7 +32,7 @@ export default Component.extend({ let crumbs = []; const root = this.get('root'); const baseKey = this.get('baseKey.display') || this.get('baseKey.id'); - const baseKeyModel = this.get('baseKey.id'); + const baseKeyModel = encodePath(this.get('baseKey.id')); if (root) { crumbs.push(root); diff --git a/ui/app/components/linked-block.js b/ui/app/components/linked-block.js index d9b459ba8ee4..6f6d30c1b2d1 100644 --- a/ui/app/components/linked-block.js +++ b/ui/app/components/linked-block.js @@ -1,6 +1,7 @@ import { inject as service } from '@ember/service'; import Component from '@ember/component'; import hbs from 'htmlbars-inline-precompile'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; let LinkedBlockComponent = Component.extend({ router: service(), @@ -19,7 +20,13 @@ let LinkedBlockComponent = Component.extend({ $target.closest('button', event.currentTarget).length > 0 || $target.closest('a', event.currentTarget).length > 0; if (!isAnchorOrButton) { - const params = this.get('params'); + let params = this.get('params'); + params = params.map((param, index) => { + if (index === 0 || typeof param !== 'string') { + return param; + } + return encodePath(param); + }); const queryParams = this.get('queryParams'); if (queryParams) { params.push({ queryParams }); diff --git a/ui/app/components/navigate-input.js b/ui/app/components/navigate-input.js index b32ad6b4c2ef..3e3a5ad0efc9 100644 --- a/ui/app/components/navigate-input.js +++ b/ui/app/components/navigate-input.js @@ -5,6 +5,7 @@ import Component from '@ember/component'; import utils from 'vault/lib/key-utils'; import keys from 'vault/lib/keycodes'; import FocusOnInsertMixin from 'vault/mixins/focus-on-insert'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; const routeFor = function(type, mode) { const MODES = { @@ -43,8 +44,15 @@ export default Component.extend(FocusOnInsertMixin, { filterMatchesKey: null, firstPartialMatch: null, - transitionToRoute: function() { - this.get('router').transitionTo(...arguments); + transitionToRoute(...args) { + let params = args.map((param, index) => { + if (index === 0 || typeof param !== 'string') { + return param; + } + return encodePath(param); + }); + + this.get('router').transitionTo(...params); }, shouldFocus: false, diff --git a/ui/app/routes/vault/cluster/secrets/backend/list.js b/ui/app/routes/vault/cluster/secrets/backend/list.js index cdf5e6ac1f86..239192d21de3 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/list.js +++ b/ui/app/routes/vault/cluster/secrets/backend/list.js @@ -4,10 +4,13 @@ import Route from '@ember/routing/route'; import { getOwner } from '@ember/application'; import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends'; import { inject as service } from '@ember/service'; +import { normalizePath } from 'vault/utils/path-encoding-helpers'; const SUPPORTED_BACKENDS = supportedSecretBackends(); export default Route.extend({ + templateName: 'vault/cluster/secrets/backend/list', + pathHelp: service('path-help'), queryParams: { page: { refreshModel: true, @@ -20,13 +23,21 @@ export default Route.extend({ }, }, - templateName: 'vault/cluster/secrets/backend/list', - pathHelp: service('path-help'), + secretParam() { + let { secret } = this.paramsFor(this.routeName); + return secret ? normalizePath(secret) : ''; + }, + + enginePathParam() { + let { backend } = this.paramsFor('vault.cluster.secrets.backend'); + return backend; + }, beforeModel() { let owner = getOwner(this); - let { secret } = this.paramsFor(this.routeName); - let { backend, tab } = this.paramsFor('vault.cluster.secrets.backend'); + let secret = this.secretParam(); + let backend = this.enginePathParam(); + let { tab } = this.paramsFor('vault.cluster.secrets.backend'); let secretEngine = this.store.peekRecord('secret-engine', backend); let type = secretEngine && secretEngine.get('engineType'); if (!type || !SUPPORTED_BACKENDS.includes(type)) { @@ -58,8 +69,8 @@ export default Route.extend({ }, model(params) { - const secret = params.secret ? params.secret : ''; - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + const secret = this.secretParam() || ''; + const backend = this.enginePathParam(); const backendModel = this.modelFor('vault.cluster.secrets.backend'); return hash({ secret, @@ -89,7 +100,7 @@ export default Route.extend({ afterModel(model) { const { tab } = this.paramsFor(this.routeName); - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + const backend = this.enginePathParam(); if (!tab || tab !== 'certs') { return; } @@ -114,7 +125,7 @@ export default Route.extend({ let secretParams = this.paramsFor(this.routeName); let secret = resolvedModel.secret; let model = resolvedModel.secrets; - let { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let backend = this.enginePathParam(); let backendModel = this.store.peekRecord('secret-engine', backend); let has404 = this.get('has404'); // only clear store cache if this is a new model @@ -155,8 +166,8 @@ export default Route.extend({ actions: { error(error, transition) { - let { secret } = this.paramsFor(this.routeName); - let { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let secret = this.secretParam(); + let backend = this.enginePathParam(); let is404 = error.httpStatus === 404; let hasModel = this.controllerFor(this.routeName).get('hasModel'); 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 f87573a5dee1..f8c84e34d2be 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js +++ b/ui/app/routes/vault/cluster/secrets/backend/secret-edit.js @@ -6,11 +6,20 @@ 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 { encodePath, normalizePath } from 'vault/utils/path-encoding-helpers'; export default Route.extend(UnloadModelRoute, { pathHelp: service('path-help'), + secretParam() { + let { secret } = this.paramsFor(this.routeName); + return secret ? normalizePath(secret) : ''; + }, + enginePathParam() { + let { backend } = this.paramsFor('vault.cluster.secrets.backend'); + return backend; + }, capabilities(secret) { - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + const backend = this.enginePathParam(); let backendModel = this.modelFor('vault.cluster.secrets.backend'); let backendType = backendModel.get('engineType'); if (backendType === 'kv' || backendType === 'cubbyhole' || backendType === 'generic') { @@ -37,13 +46,13 @@ export default Route.extend(UnloadModelRoute, { // 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 - const { secret } = this.paramsFor(this.routeName); + let secret = this.secretParam(); 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); + return this.transitionTo('vault.cluster.secrets.backend.list', encodePath(parentKey)); } else { return this.transitionTo('vault.cluster.secrets.backend.list-root'); } @@ -52,7 +61,8 @@ export default Route.extend(UnloadModelRoute, { }, buildModel(secret) { - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + const backend = this.enginePathParam(); + let modelType = this.modelType(backend, secret); if (['secret', 'secret-v2'].includes(modelType)) { return resolve(); @@ -77,10 +87,10 @@ export default Route.extend(UnloadModelRoute, { }, model(params) { - let { secret } = params; - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let secret = this.secretParam(); + let backend = this.enginePathParam(); let backendModel = this.modelFor('vault.cluster.secrets.backend', backend); - const modelType = this.modelType(backend, secret); + let modelType = this.modelType(backend, secret); if (!secret) { secret = '\u0020'; @@ -139,8 +149,8 @@ export default Route.extend(UnloadModelRoute, { setupController(controller, model) { this._super(...arguments); - const { secret } = this.paramsFor(this.routeName); - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let secret = this.secretParam(); + let backend = this.enginePathParam(); const preferAdvancedEdit = this.controllerFor('vault.cluster.secrets.backend').get('preferAdvancedEdit') || false; const backendType = this.backendType(); @@ -168,8 +178,8 @@ export default Route.extend(UnloadModelRoute, { actions: { error(error) { - const { secret } = this.paramsFor(this.routeName); - const { backend } = this.paramsFor('vault.cluster.secrets.backend'); + let secret = this.secretParam(); + let backend = this.enginePathParam(); set(error, 'keyId', backend + '/' + secret); set(error, 'backend', backend); return true; From bd063f7ce134d7008b5450d94abb31a2a460775d Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 14:40:13 -0600 Subject: [PATCH 06/19] add escape-string-regexp --- ui/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/package.json b/ui/package.json index d3c34a60ba67..0253593f005a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -94,6 +94,7 @@ "ember-source": "~3.4.0", "ember-test-selectors": "^1.0.0", "ember-truth-helpers": "^2.1.0", + "escape-string-regexp": "^1.0.5", "eslint-config-prettier": "^3.1.0", "eslint-plugin-ember": "^5.2.0", "eslint-plugin-prettier": "^3.0.0", From e852029a14e95a0e728e4e026d7093a46bfded53 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Tue, 26 Feb 2019 14:40:45 -0600 Subject: [PATCH 07/19] use list-controller mixin and escape the string when contructing new Regex objects --- .../vault/cluster/access/leases/list.js | 37 +------------------ .../vault/cluster/secrets/backend/list.js | 35 +----------------- ui/app/mixins/list-controller.js | 4 +- 3 files changed, 7 insertions(+), 69 deletions(-) diff --git a/ui/app/controllers/vault/cluster/access/leases/list.js b/ui/app/controllers/vault/cluster/access/leases/list.js index a80e513836c4..985502d25a88 100644 --- a/ui/app/controllers/vault/cluster/access/leases/list.js +++ b/ui/app/controllers/vault/cluster/access/leases/list.js @@ -2,19 +2,12 @@ import { inject as service } from '@ember/service'; import { computed } from '@ember/object'; import Controller, { inject as controller } from '@ember/controller'; import utils from 'vault/lib/key-utils'; +import ListController from 'vault/mixins/list-controller'; -export default Controller.extend({ +export default Controller.extend(ListController, { flashMessages: service(), store: service(), clusterController: controller('vault.cluster'), - queryParams: { - page: 'page', - pageFilter: 'pageFilter', - }, - - page: 1, - pageFilter: null, - filter: null, backendCrumb: computed(function() { return { @@ -27,24 +20,6 @@ export default Controller.extend({ isLoading: false, - filterMatchesKey: computed('filter', 'model', 'model.[]', function() { - var filter = this.get('filter'); - var content = this.get('model'); - return !!(content.length && content.findBy('id', filter)); - }), - - firstPartialMatch: computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() { - var filter = this.get('filter'); - var content = this.get('model'); - var filterMatchesKey = this.get('filterMatchesKey'); - var re = new RegExp('^' + filter); - return filterMatchesKey - ? null - : content.find(function(key) { - return re.test(key.get('id')); - }); - }), - filterIsFolder: computed('filter', function() { return !!utils.keyIsFolder(this.get('filter')); }), @@ -65,14 +40,6 @@ export default Controller.extend({ }), actions: { - setFilter(val) { - this.set('filter', val); - }, - - setFilterFocus(bool) { - this.set('filterFocused', bool); - }, - revokePrefix(prefix, isForce) { const adapter = this.get('store').adapterFor('lease'); const method = isForce ? 'forceRevokePrefix' : 'revokePrefix'; diff --git a/ui/app/controllers/vault/cluster/secrets/backend/list.js b/ui/app/controllers/vault/cluster/secrets/backend/list.js index 4faf8b1075ef..cfda28671fcd 100644 --- a/ui/app/controllers/vault/cluster/secrets/backend/list.js +++ b/ui/app/controllers/vault/cluster/secrets/backend/list.js @@ -4,50 +4,19 @@ import Controller from '@ember/controller'; import utils from 'vault/lib/key-utils'; import BackendCrumbMixin from 'vault/mixins/backend-crumb'; import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor'; +import ListController from 'vault/mixins/list-controller'; -export default Controller.extend(BackendCrumbMixin, WithNavToNearestAncestor, { +export default Controller.extend(ListController, BackendCrumbMixin, WithNavToNearestAncestor, { flashMessages: service(), queryParams: ['page', 'pageFilter', 'tab'], tab: '', - page: 1, - pageFilter: null, - filterFocused: false, - - // set via the route `loading` action - isLoading: false, - - filterMatchesKey: computed('filter', 'model', 'model.[]', function() { - var filter = this.get('filter'); - var content = this.get('model'); - return !!(content.length && content.findBy('id', filter)); - }), - - firstPartialMatch: computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() { - var filter = this.get('filter'); - var content = this.get('model'); - var filterMatchesKey = this.get('filterMatchesKey'); - var re = new RegExp('^' + filter); - return filterMatchesKey - ? null - : content.find(function(key) { - return re.test(key.get('id')); - }); - }), filterIsFolder: computed('filter', function() { return !!utils.keyIsFolder(this.get('filter')); }), actions: { - setFilter(val) { - this.set('filter', val); - }, - - setFilterFocus(bool) { - this.set('filterFocused', bool); - }, - chooseAction(action) { this.set('selectedAction', action); }, diff --git a/ui/app/mixins/list-controller.js b/ui/app/mixins/list-controller.js index 2c6e4ad5ae4b..423b42a12e21 100644 --- a/ui/app/mixins/list-controller.js +++ b/ui/app/mixins/list-controller.js @@ -1,5 +1,6 @@ import { computed } from '@ember/object'; import Mixin from '@ember/object/mixin'; +import escapeStringRegexp from 'escape-string-regexp'; export default Mixin.create({ queryParams: { @@ -10,6 +11,7 @@ export default Mixin.create({ page: 1, pageFilter: null, filter: null, + filterFocused: false, isLoading: false, @@ -23,7 +25,7 @@ export default Mixin.create({ var filter = this.get('filter'); var content = this.get('model'); var filterMatchesKey = this.get('filterMatchesKey'); - var re = new RegExp('^' + filter); + var re = new RegExp('^' + escapeStringRegexp(filter)); return filterMatchesKey ? null : content.find(function(key) { From 87fdc1188a835912fe62be6c48254f1994f233c2 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 11:42:52 -0600 Subject: [PATCH 08/19] update yargs parser --- ui/package.json | 2 +- ui/yarn.lock | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ui/package.json b/ui/package.json index 0253593f005a..bce9c9c03b30 100644 --- a/ui/package.json +++ b/ui/package.json @@ -114,7 +114,7 @@ "text-encoder-lite": "1.0.0", "walk-sync": "^0.3.3", "xstate": "^3.3.3", - "yargs-parser": "^10.0.0" + "yargs-parser": "^12.0.0" }, "optionalDependencies": { "husky": "^1.1.3", diff --git a/ui/yarn.lock b/ui/yarn.lock index 28bcd8bda8ed..6fc88e45310c 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -3214,6 +3214,11 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= +camelcase@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" + integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== + can-symlink@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/can-symlink/-/can-symlink-1.0.0.tgz#97b607d8a84bb6c6e228b902d864ecb594b9d219" @@ -4127,7 +4132,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -13535,13 +13540,21 @@ yam@^0.0.24: fs-extra "^4.0.2" lodash.merge "^4.6.0" -yargs-parser@^10.0.0, yargs-parser@^10.1.0: +yargs-parser@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== dependencies: camelcase "^4.1.0" +yargs-parser@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-12.0.0.tgz#18aa348854747dfe1002d01bd87d65df10d40a84" + integrity sha512-WQM8GrbF5TKiACr7iE3I2ZBNC7qC9taKPMfjJaMD2LkOJQhIctASxKXdFAOPim/m47kgAQBVIaPlFjnRdkol7w== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" From 21d032bbdd5c0e2462c2432889eda11aa4362a0e Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 14:42:37 -0600 Subject: [PATCH 09/19] add temp fork of args-parser tokenizer --- ui/app/utils/args-tokenizer.js | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ui/app/utils/args-tokenizer.js diff --git a/ui/app/utils/args-tokenizer.js b/ui/app/utils/args-tokenizer.js new file mode 100644 index 000000000000..c77b0b531bac --- /dev/null +++ b/ui/app/utils/args-tokenizer.js @@ -0,0 +1,38 @@ +export default function(argString) { + if (Array.isArray(argString)) return argString; + + argString = argString.trim(); + + var i = 0; + var prevC = null; + var c = null; + var opening = null; + var args = []; + + for (var ii = 0; ii < argString.length; ii++) { + prevC = c; + c = argString.charAt(ii); + + // split on spaces unless we're in quotes. + if (c === ' ' && !opening) { + if (!(prevC === ' ')) { + i++; + } + continue; + } + + // don't split the string if we're in matching + // opening or closing single and double quotes. + if (c === opening) { + if (!args[i]) args[i] = ''; + opening = null; + } else if ((c === "'" || c === '"') && argString.indexOf(c, ii + 1) > 0 && !opening) { + opening = c; + } + + if (!args[i]) args[i] = ''; + args[i] += c; + } + + return args; +} From 8044d0e750ae10f9da02a1e4e2e8d66d0e77089c Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 14:43:05 -0600 Subject: [PATCH 10/19] use forked tokenizer --- ui/app/lib/console-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/lib/console-helpers.js b/ui/app/lib/console-helpers.js index 211a65c5e898..f6f6d50ba29f 100644 --- a/ui/app/lib/console-helpers.js +++ b/ui/app/lib/console-helpers.js @@ -1,5 +1,5 @@ import keys from 'vault/lib/keycodes'; -import argTokenizer from 'yargs-parser/lib/tokenize-arg-string.js'; +import argTokenizer from 'vault/utils/args-tokenizer'; const supportedCommands = ['read', 'write', 'list', 'delete']; const uiCommands = ['clearall', 'clear', 'fullscreen', 'refresh']; From b14249b81e4f3f42a8f819bea8b660a54d23b475 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 14:44:11 -0600 Subject: [PATCH 11/19] encode paths in the console service --- ui/app/services/console.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/services/console.js b/ui/app/services/console.js index f9f5afd2b4c2..babac4e7b9de 100644 --- a/ui/app/services/console.js +++ b/ui/app/services/console.js @@ -5,6 +5,7 @@ import Service from '@ember/service'; import { getOwner } from '@ember/application'; import { computed } from '@ember/object'; import { shiftCommandIndex } from 'vault/lib/console-helpers'; +import { encodePath } from 'vault/utils/path-encoding-helpers'; export function sanitizePath(path) { //remove whitespace + remove trailing and leading slashes @@ -74,7 +75,7 @@ export default Service.extend({ ajax(operation, path, options = {}) { let verb = VERBS[operation]; let adapter = this.adapter(); - let url = adapter.buildURL(path); + let url = adapter.buildURL(encodePath(path)); let { data, wrapTTL } = options; return adapter.ajax(url, verb, { data, From 21f4f715d6edbbba7221813c306303e06884b9fc Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 14:44:27 -0600 Subject: [PATCH 12/19] add acceptance tests for kv secrets --- .../secrets/backend/kv/secret-test.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/ui/tests/acceptance/secrets/backend/kv/secret-test.js b/ui/tests/acceptance/secrets/backend/kv/secret-test.js index 89bd54273c28..3b48038f18e9 100644 --- a/ui/tests/acceptance/secrets/backend/kv/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/secret-test.js @@ -259,4 +259,44 @@ module('Acceptance | secrets/secret/create', function(hooks) { assert.ok(showPage.editIsPresent, 'shows the edit button'); await logout.visit(); }); + + test('paths are properly encoded', async function(assert) { + let backend = 'kv'; + let paths = [ + '(', + ')', + '"', + "'", + '!', + '#', + '$', + '&', + '*', + '+', + '@', + '{', + '|', + '}', + '~', + '[', + '\\', + ']', + '^', + '_', + ].map(char => `${char}some`); + assert.expect(paths.length * 2); + let secretName = '2'; + let commands = paths.map(path => `write ${backend}/${path}/${secretName} 3=4`); + await consoleComponent.runCommands(['write sys/mounts/kv type=kv', ...commands]); + for (let path of paths) { + await listPage.visit({ backend, id: path }); + assert.ok(listPage.secrets.filterBy('text', '2')[0], `${path}: secret is displayed properly`); + await listPage.secrets.filterBy('text', '2')[0].click(); + assert.equal( + currentRouteName(), + 'vault.cluster.secrets.backend.show', + `${path}: show page renders correctly` + ); + } + }); }); From 9ede5720482b0da33cfe633d9ac952877f29ddd3 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 17:32:31 -0600 Subject: [PATCH 13/19] revert yargs-parser upgrade --- ui/package.json | 2 +- ui/yarn.lock | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/ui/package.json b/ui/package.json index bce9c9c03b30..0253593f005a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -114,7 +114,7 @@ "text-encoder-lite": "1.0.0", "walk-sync": "^0.3.3", "xstate": "^3.3.3", - "yargs-parser": "^12.0.0" + "yargs-parser": "^10.0.0" }, "optionalDependencies": { "husky": "^1.1.3", diff --git a/ui/yarn.lock b/ui/yarn.lock index 6fc88e45310c..28bcd8bda8ed 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -3214,11 +3214,6 @@ camelcase@^4.0.0, camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -camelcase@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" - integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== - can-symlink@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/can-symlink/-/can-symlink-1.0.0.tgz#97b607d8a84bb6c6e228b902d864ecb594b9d219" @@ -4132,7 +4127,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -13540,21 +13535,13 @@ yam@^0.0.24: fs-extra "^4.0.2" lodash.merge "^4.6.0" -yargs-parser@^10.1.0: +yargs-parser@^10.0.0, yargs-parser@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== dependencies: camelcase "^4.1.0" -yargs-parser@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-12.0.0.tgz#18aa348854747dfe1002d01bd87d65df10d40a84" - integrity sha512-WQM8GrbF5TKiACr7iE3I2ZBNC7qC9taKPMfjJaMD2LkOJQhIctASxKXdFAOPim/m47kgAQBVIaPlFjnRdkol7w== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" From 5d085645c2ddd908cda4c5bba3db75dd6ffb51da Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Wed, 27 Feb 2019 17:33:48 -0600 Subject: [PATCH 14/19] make encoding in linked-block an attribute, and use it on secret lists --- ui/app/components/linked-block.js | 16 ++++++++++------ .../partials/secret-list/aws-role-item.hbs | 1 + ui/app/templates/partials/secret-list/item.hbs | 1 + .../partials/secret-list/pki-cert-item.hbs | 1 + .../partials/secret-list/pki-role-item.hbs | 1 + .../partials/secret-list/ssh-role-item.hbs | 1 + 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ui/app/components/linked-block.js b/ui/app/components/linked-block.js index 6f6d30c1b2d1..b829b2754805 100644 --- a/ui/app/components/linked-block.js +++ b/ui/app/components/linked-block.js @@ -12,6 +12,8 @@ let LinkedBlockComponent = Component.extend({ queryParams: null, + encode: false, + click(event) { const $target = this.$(event.target); const isAnchorOrButton = @@ -21,12 +23,14 @@ let LinkedBlockComponent = Component.extend({ $target.closest('a', event.currentTarget).length > 0; if (!isAnchorOrButton) { let params = this.get('params'); - params = params.map((param, index) => { - if (index === 0 || typeof param !== 'string') { - return param; - } - return encodePath(param); - }); + if (this.encode) { + params = params.map((param, index) => { + if (index === 0 || typeof param !== 'string') { + return param; + } + return encodePath(param); + }); + } const queryParams = this.get('queryParams'); if (queryParams) { params.push({ queryParams }); diff --git a/ui/app/templates/partials/secret-list/aws-role-item.hbs b/ui/app/templates/partials/secret-list/aws-role-item.hbs index 47af1119631a..54eb34357c99 100644 --- a/ui/app/templates/partials/secret-list/aws-role-item.hbs +++ b/ui/app/templates/partials/secret-list/aws-role-item.hbs @@ -7,6 +7,7 @@ item.id class="list-item-row" data-test-secret-link=item.id + encode=true }}
diff --git a/ui/app/templates/partials/secret-list/item.hbs b/ui/app/templates/partials/secret-list/item.hbs index b16b5f2d6ae5..7530e6ea882c 100644 --- a/ui/app/templates/partials/secret-list/item.hbs +++ b/ui/app/templates/partials/secret-list/item.hbs @@ -7,6 +7,7 @@ item.id class="list-item-row" data-test-secret-link=item.id + encode=true }}
diff --git a/ui/app/templates/partials/secret-list/pki-cert-item.hbs b/ui/app/templates/partials/secret-list/pki-cert-item.hbs index 76f5b5afc0e0..096705279d20 100644 --- a/ui/app/templates/partials/secret-list/pki-cert-item.hbs +++ b/ui/app/templates/partials/secret-list/pki-cert-item.hbs @@ -8,6 +8,7 @@ class="list-item-row" data-test-secret-link=item.id tagName="div" + encode=true }}
diff --git a/ui/app/templates/partials/secret-list/pki-role-item.hbs b/ui/app/templates/partials/secret-list/pki-role-item.hbs index e272074c678a..37113a740493 100644 --- a/ui/app/templates/partials/secret-list/pki-role-item.hbs +++ b/ui/app/templates/partials/secret-list/pki-role-item.hbs @@ -10,6 +10,7 @@ class="list-item-row" data-test-secret-link=item.id tagName="div" + encode=true }}
diff --git a/ui/app/templates/partials/secret-list/ssh-role-item.hbs b/ui/app/templates/partials/secret-list/ssh-role-item.hbs index caed3090d7ca..5c582ef7bb99 100644 --- a/ui/app/templates/partials/secret-list/ssh-role-item.hbs +++ b/ui/app/templates/partials/secret-list/ssh-role-item.hbs @@ -7,6 +7,7 @@ item.id class="list-item-row" data-test-secret-link=item.id + encode=true }}
From c4af1b7432372268d3886d539e0a954bd66e03bf Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Thu, 28 Feb 2019 10:35:44 -0600 Subject: [PATCH 15/19] egp endpoints are enterprise-only, so include 'enterprise' text in the test --- ui/tests/acceptance/cluster-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/acceptance/cluster-test.js b/ui/tests/acceptance/cluster-test.js index f1c633de441f..9463efef24f2 100644 --- a/ui/tests/acceptance/cluster-test.js +++ b/ui/tests/acceptance/cluster-test.js @@ -54,7 +54,7 @@ module('Acceptance | cluster', function(hooks) { await logout.visit(); }); - test('nav item links to first route that user has access to', async function(assert) { + test('enterprise nav item links to first route that user has access to', async function(assert) { const read_rgp_policy = `' path "sys/policies/rgp" { capabilities = ["read"] From 774eaf2e1ef685f0a749cd3ad63d882070fec84a Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Thu, 28 Feb 2019 10:38:09 -0600 Subject: [PATCH 16/19] fix routing test and exclude single quote from encoding tests --- ui/tests/acceptance/secrets/backend/kv/secret-test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/tests/acceptance/secrets/backend/kv/secret-test.js b/ui/tests/acceptance/secrets/backend/kv/secret-test.js index 3b48038f18e9..7c2f01ecd13f 100644 --- a/ui/tests/acceptance/secrets/backend/kv/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/secret-test.js @@ -1,4 +1,4 @@ -import { settled, currentURL, currentRouteName } from '@ember/test-helpers'; +import { visit, settled, currentURL, currentRouteName } from '@ember/test-helpers'; import { create } from 'ember-cli-page-object'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; @@ -177,7 +177,8 @@ module('Acceptance | secrets/secret/create', function(hooks) { await listPage.visitRoot({ backend: 'secret' }); await listPage.create(); await editPage.createSecret(path, 'foo', 'bar'); - await listPage.visit({ backend: 'secret', id: 'foo/bar' }); + // use visit helper here because ids with / in them get encoded + await visit('/vault/secrets/secret/list/foo/bar'); assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.list'); assert.ok(currentURL().endsWith('/'), 'redirects to the path ending in a slash'); }); @@ -266,7 +267,7 @@ module('Acceptance | secrets/secret/create', function(hooks) { '(', ')', '"', - "'", + //"'", '!', '#', '$', From 3aaa45b3f5106e227ad28d6aba0eedb3101256a1 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Thu, 28 Feb 2019 10:40:00 -0600 Subject: [PATCH 17/19] encode cli string before tokenizing --- ui/app/lib/console-helpers.js | 7 +++++-- ui/app/utils/path-encoding-helpers.js | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ui/app/lib/console-helpers.js b/ui/app/lib/console-helpers.js index f6f6d50ba29f..c0cb651f7496 100644 --- a/ui/app/lib/console-helpers.js +++ b/ui/app/lib/console-helpers.js @@ -1,5 +1,5 @@ import keys from 'vault/lib/keycodes'; -import argTokenizer from 'vault/utils/args-tokenizer'; +import argTokenizer from 'yargs-parser/lib/tokenize-arg-string.js'; const supportedCommands = ['read', 'write', 'list', 'delete']; const uiCommands = ['clearall', 'clear', 'fullscreen', 'refresh']; @@ -56,11 +56,14 @@ export function executeUICommand(command, logAndOutput, clearLog, toggleFullscre } export function parseCommand(command, shouldThrow) { - let args = argTokenizer(command); + // encode everything but spaces + let cmd = encodeURIComponent(command).replace(/%20/g, decodeURIComponent); + let args = argTokenizer(cmd); if (args[0] === 'vault') { args.shift(); } + args = args.map(decodeURIComponent); let [method, ...rest] = args; let path; let flags = []; diff --git a/ui/app/utils/path-encoding-helpers.js b/ui/app/utils/path-encoding-helpers.js index 88b254a1a3b9..ce5f082a7214 100644 --- a/ui/app/utils/path-encoding-helpers.js +++ b/ui/app/utils/path-encoding-helpers.js @@ -6,9 +6,11 @@ const { export function encodePath(path) { return path - .split('/') - .map(encodePathSegment) - .join('/'); + ? path + .split('/') + .map(encodePathSegment) + .join('/') + : path; } export { normalizePath, encodePathSegment }; From f5f660581e8506dc404ce0c192f729aa85c9f9ea Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Thu, 28 Feb 2019 10:40:20 -0600 Subject: [PATCH 18/19] encode auth_path for use with urlFor --- ui/app/adapters/role-jwt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/adapters/role-jwt.js b/ui/app/adapters/role-jwt.js index d335cc3321af..b492a376994e 100644 --- a/ui/app/adapters/role-jwt.js +++ b/ui/app/adapters/role-jwt.js @@ -8,9 +8,10 @@ export default ApplicationAdapter.extend({ findRecord(store, type, id, snapshot) { let [path, role] = JSON.parse(id); + path = encodePath(path); let namespace = get(snapshot, 'adapterOptions.namespace'); - let url = `/v1/auth/${encodePath(path)}/oidc/auth_url`; + let url = `/v1/auth/${path}/oidc/auth_url`; let redirect_uri = `${window.location.origin}${this.router.urlFor('vault.cluster.oidc-callback', { auth_path: path, })}`; From 9e972446507ddb68ae459eddffb529e9eaa2a17e Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Thu, 28 Feb 2019 11:13:59 -0600 Subject: [PATCH 19/19] add test for single quote via UI input instead of web cli --- .../secrets/backend/kv/secret-test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ui/tests/acceptance/secrets/backend/kv/secret-test.js b/ui/tests/acceptance/secrets/backend/kv/secret-test.js index 7c2f01ecd13f..355025567baa 100644 --- a/ui/tests/acceptance/secrets/backend/kv/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/secret-test.js @@ -300,4 +300,21 @@ module('Acceptance | secrets/secret/create', function(hooks) { ); } }); + + // the web cli does not handle a single quote in a path, so we test it here via the UI + test('creating a secret with a single quote works properly', async function(assert) { + await consoleComponent.runCommands('write sys/mounts/kv type=kv'); + let path = "'some"; + await listPage.visitRoot({ backend: 'kv' }); + await listPage.create(); + await editPage.createSecret(`${path}/2`, 'foo', 'bar'); + await listPage.visit({ backend: 'kv', id: path }); + assert.ok(listPage.secrets.filterBy('text', '2')[0], `${path}: secret is displayed properly`); + await listPage.secrets.filterBy('text', '2')[0].click(); + assert.equal( + currentRouteName(), + 'vault.cluster.secrets.backend.show', + `${path}: show page renders correctly` + ); + }); });