mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
UI: Update resultant-acl banner (#25256)
* Request resultant-acl only from users root namespace * Update permissions adapter to always call resultant-acl at users root, with test * Update resultant-acl to accept failType * Update permissions service to set permissionsBanner based on resultant-acl contents * wire it up * add changelog * cleanup unused adapter changes * use getter for shared namespace logic
This commit is contained in:
3
changelog/25256.txt
Normal file
3
changelog/25256.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
ui: Do not show resultant-acl banner on namespaces a user has access to
|
||||||
|
```
|
||||||
@@ -7,7 +7,8 @@ import ApplicationAdapter from './application';
|
|||||||
|
|
||||||
export default ApplicationAdapter.extend({
|
export default ApplicationAdapter.extend({
|
||||||
query() {
|
query() {
|
||||||
return this.ajax(this.urlForQuery(), 'GET');
|
const namespace = this.namespaceService.userRootNamespace || this.namespaceService.path;
|
||||||
|
return this.ajax(this.urlForQuery(), 'GET', { namespace });
|
||||||
},
|
},
|
||||||
|
|
||||||
urlForQuery() {
|
urlForQuery() {
|
||||||
|
|||||||
@@ -11,13 +11,9 @@
|
|||||||
data-test-resultant-acl-banner
|
data-test-resultant-acl-banner
|
||||||
as |A|
|
as |A|
|
||||||
>
|
>
|
||||||
<A.Title>Resultant ACL check failed</A.Title>
|
<A.Title>{{this.title}}</A.Title>
|
||||||
<A.Description>
|
<A.Description>
|
||||||
{{if
|
{{this.message}}
|
||||||
@isEnterprise
|
|
||||||
"You do not have access to resources in this namespace."
|
|
||||||
"Links might be shown that you don't have access to. Contact your administrator to update your policy."
|
|
||||||
}}
|
|
||||||
</A.Description>
|
</A.Description>
|
||||||
{{#if @isEnterprise}}
|
{{#if @isEnterprise}}
|
||||||
<A.Link::Standalone
|
<A.Link::Standalone
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { PERMISSIONS_BANNER_STATES } from 'vault/services/permissions';
|
||||||
|
|
||||||
export default class ResultantAclBannerComponent extends Component {
|
export default class ResultantAclBannerComponent extends Component {
|
||||||
@service namespace;
|
@service namespace;
|
||||||
@@ -20,4 +21,16 @@ export default class ResultantAclBannerComponent extends Component {
|
|||||||
// Bring user back to current page after login
|
// Bring user back to current page after login
|
||||||
return { redirect_to: this.router.currentURL };
|
return { redirect_to: this.router.currentURL };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.args.failType === PERMISSIONS_BANNER_STATES.noAccess
|
||||||
|
? 'You do not have access to this namespace'
|
||||||
|
: 'Resultant ACL check failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
get message() {
|
||||||
|
return this.args.failType === PERMISSIONS_BANNER_STATES.noAccess
|
||||||
|
? 'Log into the namespace directly, or contact your administrator if you think you should have access.'
|
||||||
|
: "Links might be shown that you don't have access to. Contact your administrator to update your policy.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default Controller.extend({
|
|||||||
consoleOpen: alias('console.isOpen'),
|
consoleOpen: alias('console.isOpen'),
|
||||||
activeCluster: alias('auth.activeCluster'),
|
activeCluster: alias('auth.activeCluster'),
|
||||||
|
|
||||||
permissionReadFailed: alias('permissions.readFailed'),
|
permissionBanner: alias('permissions.permissionsBanner'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleConsole() {
|
toggleConsole() {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import Service, { inject as service } from '@ember/service';
|
|||||||
import { sanitizePath, sanitizeStart } from 'core/utils/sanitize-path';
|
import { sanitizePath, sanitizeStart } from 'core/utils/sanitize-path';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
|
export const PERMISSIONS_BANNER_STATES = {
|
||||||
|
readFailed: 'read-failed',
|
||||||
|
noAccess: 'no-ns-access',
|
||||||
|
};
|
||||||
const API_PATHS = {
|
const API_PATHS = {
|
||||||
access: {
|
access: {
|
||||||
methods: 'sys/auth',
|
methods: 'sys/auth',
|
||||||
@@ -68,12 +72,19 @@ export default Service.extend({
|
|||||||
exactPaths: null,
|
exactPaths: null,
|
||||||
globPaths: null,
|
globPaths: null,
|
||||||
canViewAll: null,
|
canViewAll: null,
|
||||||
readFailed: false,
|
permissionsBanner: null,
|
||||||
chrootNamespace: null,
|
chrootNamespace: null,
|
||||||
store: service(),
|
store: service(),
|
||||||
auth: service(),
|
auth: service(),
|
||||||
namespace: service(),
|
namespace: service(),
|
||||||
|
|
||||||
|
get baseNs() {
|
||||||
|
const currentNs = this.namespace.path;
|
||||||
|
return this.chrootNamespace
|
||||||
|
? `${sanitizePath(this.chrootNamespace)}/${sanitizePath(currentNs)}`
|
||||||
|
: sanitizePath(currentNs);
|
||||||
|
},
|
||||||
|
|
||||||
getPaths: task(function* () {
|
getPaths: task(function* () {
|
||||||
if (this.paths) {
|
if (this.paths) {
|
||||||
return;
|
return;
|
||||||
@@ -86,24 +97,36 @@ export default Service.extend({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If no policy can be found, default to showing all nav items.
|
// If no policy can be found, default to showing all nav items.
|
||||||
this.set('canViewAll', true);
|
this.set('canViewAll', true);
|
||||||
this.set('readFailed', true);
|
this.set('permissionsBanner', PERMISSIONS_BANNER_STATES.readFailed);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
calcNsAccess() {
|
||||||
|
if (this.canViewAll) {
|
||||||
|
this.set('permissionsBanner', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const namespace = this.baseNs;
|
||||||
|
const allowed =
|
||||||
|
Object.keys(this.globPaths).any((k) => k.startsWith(namespace)) ||
|
||||||
|
Object.keys(this.exactPaths).any((k) => k.startsWith(namespace));
|
||||||
|
this.set('permissionsBanner', allowed ? null : PERMISSIONS_BANNER_STATES.noAccess);
|
||||||
|
},
|
||||||
|
|
||||||
setPaths(resp) {
|
setPaths(resp) {
|
||||||
this.set('exactPaths', resp.data.exact_paths);
|
this.set('exactPaths', resp.data.exact_paths);
|
||||||
this.set('globPaths', resp.data.glob_paths);
|
this.set('globPaths', resp.data.glob_paths);
|
||||||
this.set('canViewAll', resp.data.root);
|
this.set('canViewAll', resp.data.root);
|
||||||
this.set('chrootNamespace', resp.data.chroot_namespace);
|
this.set('chrootNamespace', resp.data.chroot_namespace);
|
||||||
this.set('readFailed', false);
|
this.calcNsAccess();
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.set('exactPaths', null);
|
this.set('exactPaths', null);
|
||||||
this.set('globPaths', null);
|
this.set('globPaths', null);
|
||||||
this.set('canViewAll', null);
|
this.set('canViewAll', null);
|
||||||
this.set('readFailed', false);
|
|
||||||
this.set('chrootNamespace', null);
|
this.set('chrootNamespace', null);
|
||||||
|
this.set('permissionsBanner', null);
|
||||||
},
|
},
|
||||||
|
|
||||||
hasNavPermission(navItem, routeParams, requireAll) {
|
hasNavPermission(navItem, routeParams, requireAll) {
|
||||||
@@ -131,9 +154,7 @@ export default Service.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
pathNameWithNamespace(pathName) {
|
pathNameWithNamespace(pathName) {
|
||||||
const namespace = this.chrootNamespace
|
const namespace = this.baseNs;
|
||||||
? `${sanitizePath(this.chrootNamespace)}/${sanitizePath(this.namespace.path)}`
|
|
||||||
: sanitizePath(this.namespace.path);
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
return `${sanitizePath(namespace)}/${sanitizeStart(pathName)}`;
|
return `${sanitizePath(namespace)}/${sanitizeStart(pathName)}`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
|
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if this.permissionReadFailed}}
|
{{#if this.permissionBanner}}
|
||||||
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} />
|
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} @failType={{this.permissionBanner}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="global-flash">
|
<div class="global-flash">
|
||||||
|
|||||||
@@ -7,35 +7,51 @@ import { module, test } from 'qunit';
|
|||||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||||
import { click, render } from '@ember/test-helpers';
|
import { click, render } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
import { PERMISSIONS_BANNER_STATES } from 'vault/services/permissions';
|
||||||
|
|
||||||
|
const TEXT = {
|
||||||
|
titleReadFail: 'Resultant ACL check failed',
|
||||||
|
titleNoAccess: 'You do not have access to this namespace',
|
||||||
|
messageReadFail:
|
||||||
|
"Links might be shown that you don't have access to. Contact your administrator to update your policy.",
|
||||||
|
messageNoAccess:
|
||||||
|
'Log into the namespace directly, or contact your administrator if you think you should have access.',
|
||||||
|
};
|
||||||
module('Integration | Component | resultant-acl-banner', function (hooks) {
|
module('Integration | Component | resultant-acl-banner', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test('it renders correctly by default', async function (assert) {
|
test('it renders correctly by default', async function (assert) {
|
||||||
await render(hbs`<ResultantAclBanner />`);
|
await render(hbs`<ResultantAclBanner />`);
|
||||||
|
|
||||||
assert.dom('[data-test-resultant-acl-banner] .hds-alert__title').hasText('Resultant ACL check failed');
|
assert.dom('[data-test-resultant-acl-banner] .hds-alert__title').hasText(TEXT.titleReadFail);
|
||||||
assert
|
assert.dom('[data-test-resultant-acl-banner] .hds-alert__description').hasText(TEXT.messageReadFail);
|
||||||
.dom('[data-test-resultant-acl-banner] .hds-alert__description')
|
|
||||||
.hasText(
|
|
||||||
"Links might be shown that you don't have access to. Contact your administrator to update your policy."
|
|
||||||
);
|
|
||||||
assert.dom('[data-test-resultant-acl-reauthenticate]').doesNotExist('Does not show reauth link');
|
assert.dom('[data-test-resultant-acl-reauthenticate]').doesNotExist('Does not show reauth link');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders correctly with set namespace', async function (assert) {
|
test('it renders correctly with set namespace', async function (assert) {
|
||||||
const nsService = this.owner.lookup('service:namespace');
|
const nsService = this.owner.lookup('service:namespace');
|
||||||
nsService.setNamespace('my-ns');
|
nsService.setNamespace('my-ns');
|
||||||
|
this.set('failType', undefined);
|
||||||
|
|
||||||
await render(hbs`<ResultantAclBanner @isEnterprise={{true}} />`);
|
await render(hbs`<ResultantAclBanner @isEnterprise={{true}} @failType={{this.failType}} />`);
|
||||||
|
|
||||||
assert.dom('[data-test-resultant-acl-banner] .hds-alert__title').hasText('Resultant ACL check failed');
|
assert
|
||||||
|
.dom('[data-test-resultant-acl-banner] .hds-alert__title')
|
||||||
|
.hasText(TEXT.titleReadFail, 'title correct for default fail type');
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-resultant-acl-banner] .hds-alert__description')
|
.dom('[data-test-resultant-acl-banner] .hds-alert__description')
|
||||||
.hasText('You do not have access to resources in this namespace.');
|
.hasText(TEXT.messageReadFail, 'message correct for default fail type');
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-resultant-acl-reauthenticate]')
|
.dom('[data-test-resultant-acl-reauthenticate]')
|
||||||
.hasText('Log into my-ns namespace', 'Shows reauth link with given namespace');
|
.hasText('Log into my-ns namespace', 'Shows reauth link with given namespace');
|
||||||
|
|
||||||
|
this.set('failType', PERMISSIONS_BANNER_STATES.noAccess);
|
||||||
|
assert
|
||||||
|
.dom('[data-test-resultant-acl-banner] .hds-alert__title')
|
||||||
|
.hasText(TEXT.titleNoAccess, 'title correct for no access failtype');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-resultant-acl-banner] .hds-alert__description')
|
||||||
|
.hasText(TEXT.messageNoAccess, 'message correct for no access failtype');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it renders correctly with default namespace', async function (assert) {
|
test('it renders correctly with default namespace', async function (assert) {
|
||||||
|
|||||||
37
ui/tests/unit/adapters/permissions-test.js
Normal file
37
ui/tests/unit/adapters/permissions-test.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
|
||||||
|
module('Unit | Adapter | permissions', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
test('it calls resultant-acl with the users root namespace', async function (assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
const adapter = this.owner.lookup('adapter:permissions');
|
||||||
|
const nsService = this.owner.lookup('service:namespace');
|
||||||
|
nsService.setNamespace('admin/foo');
|
||||||
|
nsService.reopen({
|
||||||
|
userRootNamespace: 'admin/bar',
|
||||||
|
});
|
||||||
|
this.server.get('/sys/internal/ui/resultant-acl', (schema, request) => {
|
||||||
|
assert.strictEqual(
|
||||||
|
request.requestHeaders['X-Vault-Namespace'],
|
||||||
|
'admin/bar',
|
||||||
|
'Namespace is users root not current path'
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
exact_paths: {},
|
||||||
|
glob_paths: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await adapter.query();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user