Skip to content

Commit

Permalink
UI: always send capabilities-self request in user's root namespace (#…
Browse files Browse the repository at this point in the history
…24168)

* Add getRelativePath helper and use to calculate relativeNamespace

* Always request capabilities-self on users root ns and prefix body with relative path

* Update capabilities adapter with test

* add changelog

* Simplify getRelativePath logic

* test update
  • Loading branch information
hashishaw authored Nov 20, 2023
1 parent 4cf837d commit b833b30
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 3 deletions.
3 changes: 3 additions & 0 deletions changelog/24168.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: capabilities-self is always called in the user's root namespace
```
16 changes: 15 additions & 1 deletion ui/app/adapters/capabilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,28 @@
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 { relativeNamespace } = this.namespaceService;
if (!relativeNamespace) {
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');
}
Expand Down
8 changes: 8 additions & 0 deletions ui/app/services/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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', '');
Expand Down
20 changes: 20 additions & 0 deletions ui/lib/core/addon/utils/sanitize-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@
*/

export function sanitizePath(path) {
if (!path) return '';
//remove whitespace + remove trailing and leading slashes
return path.trim().replace(/^\/+|\/+$/g, '');
}

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.
* 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
*/
export function getRelativePath(fullPath = '', rootPath = '') {
const root = sanitizePath(rootPath);
const full = sanitizePath(fullPath);

if (!root) {
return full;
} else if (root === full) {
return '';
}
return sanitizePath(full.substring(root.length));
}
2 changes: 1 addition & 1 deletion ui/lib/core/app/utils/sanitize-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
41 changes: 41 additions & 0 deletions ui/tests/unit/adapters/capabilities-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
11 changes: 10 additions & 1 deletion ui/tests/unit/utils/sanitize-path-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -14,10 +14,19 @@ 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');
assert.strictEqual(getRelativePath('/admin/bop/boop/admin_foo/baz/', 'admin'), 'bop/boop/admin_foo/baz');
});
});

0 comments on commit b833b30

Please sign in to comment.