diff --git a/ui/app/services/permissions.js b/ui/app/services/permissions.js index c5e8064025d9..3233d095996b 100644 --- a/ui/app/services/permissions.js +++ b/ui/app/services/permissions.js @@ -82,6 +82,12 @@ const API_PATHS_TO_ROUTE_PARAMS = { root: boolean; chroot_namespace?: string; }; + There are a couple nuances to be aware of about this response. When a + chroot_namespace is set, all of the paths in the response will be prefixed + with that namespace. Additionally, this endpoint is only added to the default + policy in the user's root namespace, so we make the call to the user's root + namespace (the namespace where the user's auth method is mounted) no matter + what the current namespace is. */ export default class PermissionsService extends Service { @@ -91,7 +97,6 @@ export default class PermissionsService extends Service { @tracked permissionsBanner = null; @tracked chrootNamespace = null; @service store; - @service auth; @service namespace; get baseNs() { @@ -117,6 +122,27 @@ export default class PermissionsService extends Service { } } + get wildcardPath() { + const ns = [sanitizePath(this.chrootNamespace), sanitizePath(this.namespace.userRootNamespace)].join('/'); + // wildcard path comes back from root namespace as empty string, + // but within a namespace it's the namespace itself ending with a slash + return ns === '/' ? '' : `${sanitizePath(ns)}/`; + } + + /** + * hasWildcardAccess checks if the user has a wildcard policy + * @param {object} globPaths key is path, value is object with capabilities + * @returns {boolean} whether the user's policy includes wildcard access to NS + */ + hasWildcardAccess(globPaths = {}) { + // First check if the wildcard path is in the globPaths object + if (!Object.keys(globPaths).includes(this.wildcardPath)) return false; + + // if so, make sure the current namespace is a child of the wildcard path + return this.namespace.path.startsWith(this.wildcardPath); + } + + // This method is called to recalculate whether to show the permissionsBanner when the namespace changes calcNsAccess() { if (this.canViewAll) { this.permissionsBanner = null; @@ -124,7 +150,11 @@ export default class PermissionsService extends Service { } const namespace = this.baseNs; const allowed = + // check if the user has wildcard access to the relative root namespace + this.hasWildcardAccess(this.globPaths) || + // or if any of their glob paths start with the namespace Object.keys(this.globPaths).any((k) => k.startsWith(namespace)) || + // or if any of their exact paths start with the namespace Object.keys(this.exactPaths).any((k) => k.startsWith(namespace)); this.permissionsBanner = allowed ? null : PERMISSIONS_BANNER_STATES.noAccess; } diff --git a/ui/app/templates/vault/cluster.hbs b/ui/app/templates/vault/cluster.hbs index 49d7c35978b6..4fa86a248db8 100644 --- a/ui/app/templates/vault/cluster.hbs +++ b/ui/app/templates/vault/cluster.hbs @@ -70,9 +70,6 @@ @autoloaded={{eq this.activeCluster.licenseState "autoloaded"}} /> {{/if}} - {{#if this.permissionBanner}} - - {{/if}}
{{#each this.flashMessages.queue as |flash|}} @@ -84,6 +81,11 @@ {{#if this.auth.isActiveSession}} + {{#if this.permissionBanner}} +
+ +
+ {{/if}} {{outlet}}
{{else}} diff --git a/ui/tests/unit/services/permissions-test.js b/ui/tests/unit/services/permissions-test.js index c9483fc365ac..ebab76f02cb3 100644 --- a/ui/tests/unit/services/permissions-test.js +++ b/ui/tests/unit/services/permissions-test.js @@ -250,4 +250,138 @@ module('Unit | Service | permissions', function (hooks) { ); }); }); + + module('wildcardPath calculates correctly', function () { + [ + { + scenario: 'no user root or chroot', + userRoot: '', + chroot: null, + expectedPath: '', + }, + { + scenario: 'user root = child ns and no chroot', + userRoot: 'bar', + chroot: null, + expectedPath: 'bar/', + }, + { + scenario: 'user root = child ns and chroot set', + userRoot: 'bar', + chroot: 'admin/', + expectedPath: 'admin/bar/', + }, + { + scenario: 'no user root and chroot set', + userRoot: '', + chroot: 'admin/', + expectedPath: 'admin/', + }, + ].forEach((testCase) => { + test(`when ${testCase.scenario}`, function (assert) { + const namespaceService = Service.extend({ + userRootNamespace: testCase.userRoot, + path: 'current/path/does/not/matter', + }); + this.owner.register('service:namespace', namespaceService); + this.service.set('chrootNamespace', testCase.chroot); + assert.strictEqual(this.service.wildcardPath, testCase.expectedPath); + }); + }); + test('when user root =child ns and chroot set', function (assert) { + const namespaceService = Service.extend({ + path: 'bar/baz', + userRootNamespace: 'bar', + }); + this.owner.register('service:namespace', namespaceService); + this.service.set('chrootNamespace', 'admin/'); + assert.strictEqual(this.service.wildcardPath, 'admin/bar/'); + }); + }); + + module('hasWildcardAccess calculates correctly', function () { + // The resultant-acl endpoint returns paths with chroot and + // relative root prefixed on all paths. + [ + { + scenario: 'when root wildcard in root namespace', + chroot: null, + userRoot: '', + currentNs: 'foo/bar', + globs: { + '': { capabilities: ['read'] }, + }, + expectedAccess: true, + }, + { + scenario: 'when root wildcard in chroot ns', + chroot: 'admin/', + userRoot: '', + currentNs: 'admin/child', + globs: { + 'admin/': { capabilities: ['read'] }, + }, + expectedAccess: true, + }, + { + scenario: 'when namespace wildcard in child ns', + chroot: null, + userRoot: 'bar', + currentNs: 'bar/baz', + globs: { + 'bar/': { capabilities: ['read'] }, + }, + expectedAccess: true, + }, + { + scenario: 'when namespace wildcard in child ns & chroot', + chroot: 'foo/', + userRoot: 'bar', + currentNs: 'foo/bar/baz', + globs: { + 'foo/bar/': { capabilities: ['read'] }, + }, + expectedAccess: true, + }, + { + scenario: 'when namespace wildcard in different ns with chroot & user root', + chroot: 'foo/', + userRoot: 'bar', + currentNs: 'foo/bing', + globs: { + 'foo/bar/': { capabilities: ['read'] }, + }, + expectedAccess: false, + }, + { + scenario: 'when namespace wildcard in different ns without chroot', + chroot: null, + userRoot: 'bar', + currentNs: 'foo/bing', + globs: { + 'bar/': { capabilities: ['read'] }, + }, + expectedAccess: false, + }, + { + scenario: 'when globs is empty', + chroot: 'foo/', + userRoot: 'bar', + currentNs: 'foo/bing', + globs: {}, + expectedAccess: false, + }, + ].forEach((testCase) => { + test(`when ${testCase.scenario}`, function (assert) { + const namespaceService = Service.extend({ + path: testCase.currentNs, + userRootNamespace: testCase.userRoot, + }); + this.owner.register('service:namespace', namespaceService); + this.service.set('chrootNamespace', testCase.chroot); + const result = this.service.hasWildcardAccess(testCase.globs); + assert.strictEqual(result, testCase.expectedAccess); + }); + }); + }); });