From 694e5374871ae8da9c6a6276aef06eb0fc036f7d Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 16 Nov 2023 12:13:50 -0600 Subject: [PATCH 1/6] Add getRelativePath helper and use to calculate relativeNamespace --- ui/app/services/namespace.js | 8 ++++++ ui/lib/core/addon/utils/sanitize-path.js | 30 +++++++++++++++++++++++ ui/lib/core/app/utils/sanitize-path.js | 2 +- ui/tests/unit/utils/sanitize-path-test.js | 10 +++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/ui/app/services/namespace.js b/ui/app/services/namespace.js index 07802320a031..cd8e65e67e05 100644 --- a/ui/app/services/namespace.js +++ b/ui/app/services/namespace.js @@ -7,6 +7,7 @@ import { alias, equal } from '@ember/object/computed'; import Service, { inject as service } from '@ember/service'; import { task } from 'ember-concurrency'; import { computed } from '@ember/object'; +import { getRelativePath } from 'core/utils/sanitize-path'; const ROOT_NAMESPACE = ''; export default Service.extend({ @@ -28,6 +29,13 @@ export default Service.extend({ return parts[parts.length - 1]; }), + relativeNamespace: computed('path', 'userRootNamespace', function () { + // relative namespace is the current namespace minus the user's root. + // so if we're in app/staging/group1 but the user's root is app, the + // relative namespace is staging/group + return getRelativePath(this.path, this.userRootNamespace); + }), + setNamespace(path) { if (!path) { this.set('path', ''); diff --git a/ui/lib/core/addon/utils/sanitize-path.js b/ui/lib/core/addon/utils/sanitize-path.js index bfd200a362d5..31338534ff8a 100644 --- a/ui/lib/core/addon/utils/sanitize-path.js +++ b/ui/lib/core/addon/utils/sanitize-path.js @@ -4,6 +4,7 @@ */ export function sanitizePath(path) { + if (!path) return ''; //remove whitespace + remove trailing and leading slashes return path.trim().replace(/^\/+|\/+$/g, ''); } @@ -11,3 +12,32 @@ export function sanitizePath(path) { export function ensureTrailingSlash(path) { return path.replace(/(\w+[^/]$)/g, '$1/'); } + +/** + * getRelativePath is for removing matching segments of a subpath from the front of a full path + * @param {string} fullPath eg apps/prod/app_1/test + * @param {string} rootPath eg apps/prod + * @returns the leftover segment, eg app_1/test + */ +export function getRelativePath(fullPath = '', rootPath = '') { + const root = sanitizePath(rootPath); + const full = sanitizePath(fullPath); + + if (!root) { + return full; + } + + return root + .split('/') + .reduce( + (prev, curr) => { + if (prev[0] === curr) { + // if they match, shift from the front + prev.shift(); + } + return prev; + }, + [...full.split('/')] + ) + .join('/'); +} diff --git a/ui/lib/core/app/utils/sanitize-path.js b/ui/lib/core/app/utils/sanitize-path.js index 58a60b33d422..27a0db30e1c6 100644 --- a/ui/lib/core/app/utils/sanitize-path.js +++ b/ui/lib/core/app/utils/sanitize-path.js @@ -3,4 +3,4 @@ * SPDX-License-Identifier: BUSL-1.1 */ -export { ensureTrailingSlash, sanitizePath } from 'core/utils/sanitize-path'; +export { ensureTrailingSlash, sanitizePath, getRelativePath } from 'core/utils/sanitize-path'; diff --git a/ui/tests/unit/utils/sanitize-path-test.js b/ui/tests/unit/utils/sanitize-path-test.js index cdde888100c9..812f8a5a27c5 100644 --- a/ui/tests/unit/utils/sanitize-path-test.js +++ b/ui/tests/unit/utils/sanitize-path-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { ensureTrailingSlash, sanitizePath } from 'core/utils/sanitize-path'; +import { ensureTrailingSlash, getRelativePath, sanitizePath } from 'core/utils/sanitize-path'; module('Unit | Utility | sanitize-path', function () { test('it removes spaces and slashes from either side', function (assert) { @@ -14,10 +14,18 @@ module('Unit | Utility | sanitize-path', function () { 'removes spaces and slashes on either side' ); assert.strictEqual(sanitizePath('//foo/bar/baz/'), 'foo/bar/baz', 'removes more than one slash'); + assert.strictEqual(sanitizePath(undefined), '', 'handles falsey values'); }); 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'); }); + + test('#getRelativePath', function (assert) { + assert.strictEqual(getRelativePath('/', undefined), '', 'works with minimal inputs'); + assert.strictEqual(getRelativePath('/baz/bar/', undefined), 'baz/bar', 'sanitizes the output'); + assert.strictEqual(getRelativePath('recipes/cookies/choc-chip/', 'recipes/'), 'cookies/choc-chip'); + assert.strictEqual(getRelativePath('/recipes/cookies/choc-chip/', 'recipes/cookies'), 'choc-chip'); + }); }); From dd12cd4f48c509aad8193f60750efbb88213cc59 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 16 Nov 2023 12:29:06 -0600 Subject: [PATCH 2/6] Always request capabilities-self on users root ns and prefix body with relative path --- ui/app/adapters/capabilities.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/app/adapters/capabilities.js b/ui/app/adapters/capabilities.js index de54824bbb0f..d2044fd20250 100644 --- a/ui/app/adapters/capabilities.js +++ b/ui/app/adapters/capabilities.js @@ -6,14 +6,29 @@ import AdapterError from '@ember-data/adapter/error'; import { set } from '@ember/object'; import ApplicationAdapter from './application'; +import { sanitizePath } from 'core/utils/sanitize-path'; export default ApplicationAdapter.extend({ pathForType() { return 'capabilities-self'; }, + formatPaths(path) { + const { path: ns, userRootNamespace, relativeNamespace } = this.namespaceService; + if (userRootNamespace === ns) { + // If they match, the user is in their root namespace and we can fetch normally + return [path]; + } + // ensure original path doesn't have leading slash + return [`/${relativeNamespace}/${path.replace(/^\//, '')}`]; + }, + findRecord(store, type, id) { - return this.ajax(this.buildURL(type), 'POST', { data: { paths: [id] } }).catch((e) => { + const paths = this.formatPaths(id); + return this.ajax(this.buildURL(type), 'POST', { + data: { paths }, + namespace: sanitizePath(this.namespaceService.userRootNamespace), + }).catch((e) => { if (e instanceof AdapterError) { set(e, 'policyPath', 'sys/capabilities-self'); } From 183dcf5e3fe2db92c01f3fb15d35ab47a79d12b0 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 16 Nov 2023 13:17:38 -0600 Subject: [PATCH 3/6] Update capabilities adapter with test --- ui/app/adapters/capabilities.js | 7 ++-- ui/tests/unit/adapters/capabilities-test.js | 41 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/ui/app/adapters/capabilities.js b/ui/app/adapters/capabilities.js index d2044fd20250..8eee53c3e27b 100644 --- a/ui/app/adapters/capabilities.js +++ b/ui/app/adapters/capabilities.js @@ -14,13 +14,12 @@ export default ApplicationAdapter.extend({ }, formatPaths(path) { - const { path: ns, userRootNamespace, relativeNamespace } = this.namespaceService; - if (userRootNamespace === ns) { - // If they match, the user is in their root namespace and we can fetch normally + const { relativeNamespace } = this.namespaceService; + if (!relativeNamespace) { return [path]; } // ensure original path doesn't have leading slash - return [`/${relativeNamespace}/${path.replace(/^\//, '')}`]; + return [`${relativeNamespace}/${path.replace(/^\//, '')}`]; }, findRecord(store, type, id) { diff --git a/ui/tests/unit/adapters/capabilities-test.js b/ui/tests/unit/adapters/capabilities-test.js index bcd1f055b551..2e0bb0f33092 100644 --- a/ui/tests/unit/adapters/capabilities-test.js +++ b/ui/tests/unit/adapters/capabilities-test.js @@ -24,4 +24,45 @@ module('Unit | Adapter | capabilities', function (hooks) { assert.deepEqual({ paths: ['foo'] }, options.data, 'data params OK'); assert.strictEqual(method, 'POST', 'method OK'); }); + + test('enterprise calls the correct url within namespace when userRoot = root', function (assert) { + const namespaceSvc = this.owner.lookup('service:namespace'); + namespaceSvc.setNamespace('admin'); + + let url, method, options; + const adapter = this.owner.factoryFor('adapter:capabilities').create({ + ajax: (...args) => { + [url, method, options] = args; + return resolve(); + }, + }); + + adapter.findRecord(null, 'capabilities', 'foo'); + assert.strictEqual(url, '/v1/sys/capabilities-self', 'calls the correct URL'); + assert.deepEqual({ paths: ['admin/foo'] }, options.data, 'data params prefix paths with namespace'); + assert.strictEqual(options.namespace, '', 'sent with root namespace'); + assert.strictEqual(method, 'POST', 'method OK'); + }); + + test('enterprise calls the correct url within namespace when userRoot is not root', function (assert) { + const namespaceSvc = this.owner.lookup('service:namespace'); + namespaceSvc.setNamespace('admin/bar/baz'); + namespaceSvc.reopen({ + userRootNamespace: 'admin/bar', + }); + + let url, method, options; + const adapter = this.owner.factoryFor('adapter:capabilities').create({ + ajax: (...args) => { + [url, method, options] = args; + return resolve(); + }, + }); + + adapter.findRecord(null, 'capabilities', 'foo'); + assert.strictEqual(url, '/v1/sys/capabilities-self', 'calls the correct URL'); + assert.deepEqual({ paths: ['baz/foo'] }, options.data, 'data params prefix path with relative namespace'); + assert.strictEqual(options.namespace, 'admin/bar', 'sent with root namespace'); + assert.strictEqual(method, 'POST', 'method OK'); + }); }); From 0fd39c861ee522b3a143faf5e13547010aa6a6b2 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 16 Nov 2023 13:30:05 -0600 Subject: [PATCH 4/6] add changelog --- changelog/24168.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/24168.txt diff --git a/changelog/24168.txt b/changelog/24168.txt new file mode 100644 index 000000000000..09f34ce8621c --- /dev/null +++ b/changelog/24168.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: capabilities-self is always called in the user's root namespace +``` \ No newline at end of file From 9eb153d3fcde0fad0c8a5de6548831fa981e0a94 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 20 Nov 2023 11:26:14 -0600 Subject: [PATCH 5/6] Simplify getRelativePath logic --- ui/lib/core/addon/utils/sanitize-path.js | 20 +++++--------------- ui/tests/unit/utils/sanitize-path-test.js | 1 + 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/ui/lib/core/addon/utils/sanitize-path.js b/ui/lib/core/addon/utils/sanitize-path.js index 31338534ff8a..7ca66c651de6 100644 --- a/ui/lib/core/addon/utils/sanitize-path.js +++ b/ui/lib/core/addon/utils/sanitize-path.js @@ -14,7 +14,8 @@ export function ensureTrailingSlash(path) { } /** - * getRelativePath is for removing matching segments of a subpath from the front of a full path + * getRelativePath is for removing matching segments of a subpath from the front of a full path. + * This method assumes that the full path starts with all of the root path. * @param {string} fullPath eg apps/prod/app_1/test * @param {string} rootPath eg apps/prod * @returns the leftover segment, eg app_1/test @@ -25,19 +26,8 @@ export function getRelativePath(fullPath = '', rootPath = '') { if (!root) { return full; + } else if (root === full) { + return ''; } - - return root - .split('/') - .reduce( - (prev, curr) => { - if (prev[0] === curr) { - // if they match, shift from the front - prev.shift(); - } - return prev; - }, - [...full.split('/')] - ) - .join('/'); + return sanitizePath(full.substring(root.length)); } diff --git a/ui/tests/unit/utils/sanitize-path-test.js b/ui/tests/unit/utils/sanitize-path-test.js index 812f8a5a27c5..d2a3b1bbf983 100644 --- a/ui/tests/unit/utils/sanitize-path-test.js +++ b/ui/tests/unit/utils/sanitize-path-test.js @@ -27,5 +27,6 @@ module('Unit | Utility | sanitize-path', function () { assert.strictEqual(getRelativePath('/baz/bar/', undefined), 'baz/bar', 'sanitizes the output'); assert.strictEqual(getRelativePath('recipes/cookies/choc-chip/', 'recipes/'), 'cookies/choc-chip'); assert.strictEqual(getRelativePath('/recipes/cookies/choc-chip/', 'recipes/cookies'), 'choc-chip'); + assert.strictEqual(getRelativePath('/beep/bop/boop/beep/bop/baz/', 'beep/bop'), 'boop/beep/bop/baz'); }); }); From bc1e8336595acb05b88122a80c22c2b3d39ad4c5 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Mon, 20 Nov 2023 11:35:15 -0600 Subject: [PATCH 6/6] test update --- ui/tests/unit/utils/sanitize-path-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/tests/unit/utils/sanitize-path-test.js b/ui/tests/unit/utils/sanitize-path-test.js index d2a3b1bbf983..7107d737f2c5 100644 --- a/ui/tests/unit/utils/sanitize-path-test.js +++ b/ui/tests/unit/utils/sanitize-path-test.js @@ -27,6 +27,6 @@ module('Unit | Utility | sanitize-path', function () { assert.strictEqual(getRelativePath('/baz/bar/', undefined), 'baz/bar', 'sanitizes the output'); assert.strictEqual(getRelativePath('recipes/cookies/choc-chip/', 'recipes/'), 'cookies/choc-chip'); assert.strictEqual(getRelativePath('/recipes/cookies/choc-chip/', 'recipes/cookies'), 'choc-chip'); - assert.strictEqual(getRelativePath('/beep/bop/boop/beep/bop/baz/', 'beep/bop'), 'boop/beep/bop/baz'); + assert.strictEqual(getRelativePath('/admin/bop/boop/admin_foo/baz/', 'admin'), 'bop/boop/admin_foo/baz'); }); });