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);
+ });
+ });
+ });
});