mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-30 07:01:14 +01:00
UI: Don't show Resultant-ACL banner when wildcard policy present (#26233)
* Add wildcard calc helpers to permissions service with tests * Check for wildcard access when calculating permissionsBanner * Move resultant-acl banner within TokenExpireWarning so it's mutually exclusive with token expired banner * fix permissions banner if statement * Add margin to resultant-acl * cleanup comments
This commit is contained in:
parent
dbe6e4ee2d
commit
f8f40c3d16
@ -82,6 +82,12 @@ const API_PATHS_TO_ROUTE_PARAMS = {
|
|||||||
root: boolean;
|
root: boolean;
|
||||||
chroot_namespace?: string;
|
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 {
|
export default class PermissionsService extends Service {
|
||||||
@ -91,7 +97,6 @@ export default class PermissionsService extends Service {
|
|||||||
@tracked permissionsBanner = null;
|
@tracked permissionsBanner = null;
|
||||||
@tracked chrootNamespace = null;
|
@tracked chrootNamespace = null;
|
||||||
@service store;
|
@service store;
|
||||||
@service auth;
|
|
||||||
@service namespace;
|
@service namespace;
|
||||||
|
|
||||||
get baseNs() {
|
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() {
|
calcNsAccess() {
|
||||||
if (this.canViewAll) {
|
if (this.canViewAll) {
|
||||||
this.permissionsBanner = null;
|
this.permissionsBanner = null;
|
||||||
@ -124,7 +150,11 @@ export default class PermissionsService extends Service {
|
|||||||
}
|
}
|
||||||
const namespace = this.baseNs;
|
const namespace = this.baseNs;
|
||||||
const allowed =
|
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)) ||
|
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));
|
Object.keys(this.exactPaths).any((k) => k.startsWith(namespace));
|
||||||
this.permissionsBanner = allowed ? null : PERMISSIONS_BANNER_STATES.noAccess;
|
this.permissionsBanner = allowed ? null : PERMISSIONS_BANNER_STATES.noAccess;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,9 +70,6 @@
|
|||||||
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
|
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if this.permissionBanner}}
|
|
||||||
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} @failType={{this.permissionBanner}} />
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="global-flash">
|
<div class="global-flash">
|
||||||
{{#each this.flashMessages.queue as |flash|}}
|
{{#each this.flashMessages.queue as |flash|}}
|
||||||
@ -84,6 +81,11 @@
|
|||||||
|
|
||||||
{{#if this.auth.isActiveSession}}
|
{{#if this.auth.isActiveSession}}
|
||||||
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}} @allowingExpiration={{this.auth.allowExpiration}}>
|
<TokenExpireWarning @expirationDate={{this.auth.tokenExpirationDate}} @allowingExpiration={{this.auth.allowExpiration}}>
|
||||||
|
{{#if this.permissionBanner}}
|
||||||
|
<div class="has-top-margin-m">
|
||||||
|
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} @failType={{this.permissionBanner}} />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</TokenExpireWarning>
|
</TokenExpireWarning>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user