mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
backport of UI: sanitize namespace input (#23507)
Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ddd4a36d83
commit
6c341d14c8
@@ -7,6 +7,7 @@ import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
import { task, timeout } from 'ember-concurrency';
|
||||
import { sanitizePath } from 'core/utils/sanitize-path';
|
||||
|
||||
export default Controller.extend({
|
||||
flashMessages: service(),
|
||||
@@ -24,30 +25,34 @@ export default Controller.extend({
|
||||
authMethod: '',
|
||||
oidcProvider: '',
|
||||
|
||||
get managedNamespaceChild() {
|
||||
const fullParam = this.namespaceQueryParam;
|
||||
const split = fullParam.split('/');
|
||||
if (split.length > 1) {
|
||||
split.shift();
|
||||
return `/${split.join('/')}`;
|
||||
get namespaceInput() {
|
||||
const namespaceQP = this.clusterController.namespaceQueryParam;
|
||||
if (this.managedNamespaceRoot) {
|
||||
// When managed, the user isn't allowed to edit the prefix `admin/` for their nested namespace
|
||||
const split = namespaceQP.split('/');
|
||||
if (split.length > 1) {
|
||||
split.shift();
|
||||
return `/${split.join('/')}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
return namespaceQP;
|
||||
},
|
||||
|
||||
updateManagedNamespace: task(function* (value) {
|
||||
// debounce
|
||||
yield timeout(500);
|
||||
// TODO: Move this to shared fn
|
||||
const newNamespace = `${this.managedNamespaceRoot}${value}`;
|
||||
this.namespaceService.setNamespace(newNamespace, true);
|
||||
this.set('namespaceQueryParam', newNamespace);
|
||||
}).restartable(),
|
||||
fullNamespaceFromInput(value) {
|
||||
const strippedNs = sanitizePath(value);
|
||||
if (this.managedNamespaceRoot) {
|
||||
return `${this.managedNamespaceRoot}/${strippedNs}`;
|
||||
}
|
||||
return strippedNs;
|
||||
},
|
||||
|
||||
updateNamespace: task(function* (value) {
|
||||
// debounce
|
||||
yield timeout(500);
|
||||
this.namespaceService.setNamespace(value, true);
|
||||
this.set('namespaceQueryParam', value);
|
||||
const ns = this.fullNamespaceFromInput(value);
|
||||
this.namespaceService.setNamespace(ns, true);
|
||||
this.set('namespaceQueryParam', ns);
|
||||
}).restartable(),
|
||||
|
||||
authSuccess({ isRoot, namespace }) {
|
||||
|
||||
@@ -13,14 +13,7 @@ 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
|
||||
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
export function ensureTrailingSlash(path) {
|
||||
return path.replace(/(\w+[^/]$)/g, '$1/');
|
||||
}
|
||||
import { sanitizePath, ensureTrailingSlash } from 'core/utils/sanitize-path';
|
||||
|
||||
const VERBS = {
|
||||
read: 'GET',
|
||||
|
||||
@@ -23,10 +23,7 @@ import { singularize } from 'ember-inflector';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
|
||||
import generatedItemAdapter from 'vault/adapters/generated-item-list';
|
||||
export function sanitizePath(path) {
|
||||
// remove whitespace + remove trailing and leading slashes
|
||||
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
import { sanitizePath } from 'core/utils/sanitize-path';
|
||||
|
||||
export default Service.extend({
|
||||
attrs: null,
|
||||
|
||||
@@ -49,52 +49,25 @@
|
||||
{{/if}}
|
||||
</Page.header>
|
||||
{{#unless this.mfaAuthData}}
|
||||
{{#if this.managedNamespaceRoot}}
|
||||
<Page.sub-header>
|
||||
<Toolbar>
|
||||
<div class="toolbar-namespace-picker" data-test-managed-namespace-toolbar>
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label">
|
||||
<label class="is-label" for="namespace">Namespace</label>
|
||||
</div>
|
||||
<div class="field-label">
|
||||
<span class="has-text-grey" data-test-managed-namespace-root>/{{this.managedNamespaceRoot}}</span>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
value={{this.managedNamespaceChild}}
|
||||
placeholder="/ (Default)"
|
||||
oninput={{perform this.updateManagedNamespace value="target.value"}}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
name="namespace"
|
||||
id="namespace"
|
||||
class="input"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</Page.sub-header>
|
||||
{{else if (has-feature "Namespaces")}}
|
||||
{{#if (has-feature "Namespaces")}}
|
||||
<Page.sub-header>
|
||||
<Toolbar class="toolbar-namespace-picker">
|
||||
<div class="field is-horizontal" data-test-namespace-toolbar>
|
||||
<div class="field-label is-normal">
|
||||
<label class="is-label" for="namespace">Namespace</label>
|
||||
</div>
|
||||
{{#if this.managedNamespaceRoot}}
|
||||
<div class="field-label">
|
||||
<span class="has-text-grey" data-test-managed-namespace-root>/{{this.managedNamespaceRoot}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input
|
||||
data-test-auth-form-ns-input
|
||||
value={{this.namespaceQueryParam}}
|
||||
placeholder="/ (Root)"
|
||||
value={{this.namespaceInput}}
|
||||
placeholder={{if this.managedNamespaceRoot "/ (Default)" "/ (Root)"}}
|
||||
oninput={{perform this.updateNamespace value="target.value"}}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
|
||||
8
ui/lib/core/addon/utils/sanitize-path.js
Normal file
8
ui/lib/core/addon/utils/sanitize-path.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export function sanitizePath(path) {
|
||||
//remove whitespace + remove trailing and leading slashes
|
||||
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
|
||||
export function ensureTrailingSlash(path) {
|
||||
return path.replace(/(\w+[^/]$)/g, '$1/');
|
||||
}
|
||||
1
ui/lib/core/app/utils/sanitize-path.js
Normal file
1
ui/lib/core/app/utils/sanitize-path.js
Normal file
@@ -0,0 +1 @@
|
||||
export { ensureTrailingSlash, sanitizePath } from 'core/utils/sanitize-path';
|
||||
@@ -76,15 +76,15 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
|
||||
assert.strictEqual(currentURL(), '/vault/auth?with=token', 'Does not redirect');
|
||||
assert.dom('[data-test-namespace-toolbar]').exists('Normal namespace toolbar exists');
|
||||
assert
|
||||
.dom('[data-test-managed-namespace-toolbar]')
|
||||
.doesNotExist('Managed namespace toolbar does not exist');
|
||||
.dom('[data-test-managed-namespace-root]')
|
||||
.doesNotExist('Managed namespace indicator does not exist');
|
||||
assert.dom('input#namespace').hasAttribute('placeholder', '/ (Root)');
|
||||
await fillIn('input#namespace', '/foo');
|
||||
const encodedNamespace = encodeURIComponent('/foo');
|
||||
await fillIn('input#namespace', '/foo/bar ');
|
||||
const encodedNamespace = encodeURIComponent('foo/bar');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/auth?namespace=${encodedNamespace}&with=token`,
|
||||
'Does not prepend root to namespace'
|
||||
'correctly sanitizes namespace'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,8 +41,7 @@ module('Acceptance | Enterprise | Managed namespace root', function (hooks) {
|
||||
await visit('/vault/auth');
|
||||
assert.ok(currentURL().startsWith('/vault/auth'), 'Redirected to auth');
|
||||
assert.ok(currentURL().includes('?namespace=admin'), 'with base namespace');
|
||||
assert.dom('[data-test-namespace-toolbar]').doesNotExist('Normal namespace toolbar does not exist');
|
||||
assert.dom('[data-test-managed-namespace-toolbar]').exists('Managed namespace toolbar exists');
|
||||
assert.dom('[data-test-namespace-toolbar]').exists('Namespace toolbar exists');
|
||||
assert.dom('[data-test-managed-namespace-root]').hasText('/admin', 'Shows /admin namespace prefix');
|
||||
assert.dom('input#namespace').hasAttribute('placeholder', '/ (Default)');
|
||||
await fillIn('input#namespace', '/foo');
|
||||
@@ -50,7 +49,13 @@ module('Acceptance | Enterprise | Managed namespace root', function (hooks) {
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/auth?namespace=${encodedNamespace}&with=token`,
|
||||
'Correctly prepends root to namespace'
|
||||
'Correctly prepends root to namespace when input starts with /'
|
||||
);
|
||||
await fillIn('input#namespace', 'foo');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/auth?namespace=${encodedNamespace}&with=token`,
|
||||
'Correctly prepends root to namespace when input does not start with /'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { sanitizePath, ensureTrailingSlash } from 'vault/services/console';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Unit | Service | console', function (hooks) {
|
||||
@@ -13,20 +12,6 @@ module('Unit | Service | console', function (hooks) {
|
||||
hooks.beforeEach(function () {});
|
||||
hooks.afterEach(function () {});
|
||||
|
||||
test('#sanitizePath', function (assert) {
|
||||
assert.strictEqual(
|
||||
sanitizePath(' /foo/bar/baz/ '),
|
||||
'foo/bar/baz',
|
||||
'removes spaces and slashs on either side'
|
||||
);
|
||||
assert.strictEqual(sanitizePath('//foo/bar/baz/'), 'foo/bar/baz', 'removes more than one slash');
|
||||
});
|
||||
|
||||
test('#ensureTrailingSlash', function (assert) {
|
||||
assert.strictEqual(ensureTrailingSlash('foo/bar'), 'foo/bar/', 'adds trailing slash');
|
||||
assert.strictEqual(ensureTrailingSlash('baz/'), 'baz/', 'keeps trailing slash if there is one');
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
method: 'read',
|
||||
|
||||
18
ui/tests/unit/utils/sanitize-path-test.js
Normal file
18
ui/tests/unit/utils/sanitize-path-test.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { ensureTrailingSlash, sanitizePath } from 'core/utils/sanitize-path';
|
||||
|
||||
module('Unit | Utility | sanitize-path', function () {
|
||||
test('it removes spaces and slashes from either side', function (assert) {
|
||||
assert.strictEqual(
|
||||
sanitizePath(' /foo/bar/baz/ '),
|
||||
'foo/bar/baz',
|
||||
'removes spaces and slashes on either side'
|
||||
);
|
||||
assert.strictEqual(sanitizePath('//foo/bar/baz/'), 'foo/bar/baz', 'removes more than one slash');
|
||||
});
|
||||
|
||||
test('#ensureTrailingSlash', function (assert) {
|
||||
assert.strictEqual(ensureTrailingSlash('foo/bar'), 'foo/bar/', 'adds trailing slash');
|
||||
assert.strictEqual(ensureTrailingSlash('baz/'), 'baz/', 'keeps trailing slash if there is one');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user