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:
Chelsea Shaw
2024-02-07 12:57:14 -06:00
committed by GitHub
parent 4283caaabe
commit 30aa1b4862
9 changed files with 113 additions and 26 deletions

3
changelog/25256.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
ui: Do not show resultant-acl banner on namespaces a user has access to
```

View File

@@ -7,7 +7,8 @@ import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
query() {
return this.ajax(this.urlForQuery(), 'GET');
const namespace = this.namespaceService.userRootNamespace || this.namespaceService.path;
return this.ajax(this.urlForQuery(), 'GET', { namespace });
},
urlForQuery() {

View File

@@ -11,13 +11,9 @@
data-test-resultant-acl-banner
as |A|
>
<A.Title>Resultant ACL check failed</A.Title>
<A.Title>{{this.title}}</A.Title>
<A.Description>
{{if
@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."
}}
{{this.message}}
</A.Description>
{{#if @isEnterprise}}
<A.Link::Standalone

View File

@@ -6,6 +6,7 @@
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { PERMISSIONS_BANNER_STATES } from 'vault/services/permissions';
export default class ResultantAclBannerComponent extends Component {
@service namespace;
@@ -20,4 +21,16 @@ export default class ResultantAclBannerComponent extends Component {
// Bring user back to current page after login
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.";
}
}

View File

@@ -39,7 +39,7 @@ export default Controller.extend({
consoleOpen: alias('console.isOpen'),
activeCluster: alias('auth.activeCluster'),
permissionReadFailed: alias('permissions.readFailed'),
permissionBanner: alias('permissions.permissionsBanner'),
actions: {
toggleConsole() {

View File

@@ -7,6 +7,10 @@ import Service, { inject as service } from '@ember/service';
import { sanitizePath, sanitizeStart } from 'core/utils/sanitize-path';
import { task } from 'ember-concurrency';
export const PERMISSIONS_BANNER_STATES = {
readFailed: 'read-failed',
noAccess: 'no-ns-access',
};
const API_PATHS = {
access: {
methods: 'sys/auth',
@@ -68,12 +72,19 @@ export default Service.extend({
exactPaths: null,
globPaths: null,
canViewAll: null,
readFailed: false,
permissionsBanner: null,
chrootNamespace: null,
store: service(),
auth: service(),
namespace: service(),
get baseNs() {
const currentNs = this.namespace.path;
return this.chrootNamespace
? `${sanitizePath(this.chrootNamespace)}/${sanitizePath(currentNs)}`
: sanitizePath(currentNs);
},
getPaths: task(function* () {
if (this.paths) {
return;
@@ -86,24 +97,36 @@ export default Service.extend({
} catch (err) {
// If no policy can be found, default to showing all nav items.
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) {
this.set('exactPaths', resp.data.exact_paths);
this.set('globPaths', resp.data.glob_paths);
this.set('canViewAll', resp.data.root);
this.set('chrootNamespace', resp.data.chroot_namespace);
this.set('readFailed', false);
this.calcNsAccess();
},
reset() {
this.set('exactPaths', null);
this.set('globPaths', null);
this.set('canViewAll', null);
this.set('readFailed', false);
this.set('chrootNamespace', null);
this.set('permissionsBanner', null);
},
hasNavPermission(navItem, routeParams, requireAll) {
@@ -131,9 +154,7 @@ export default Service.extend({
},
pathNameWithNamespace(pathName) {
const namespace = this.chrootNamespace
? `${sanitizePath(this.chrootNamespace)}/${sanitizePath(this.namespace.path)}`
: sanitizePath(this.namespace.path);
const namespace = this.baseNs;
if (namespace) {
return `${sanitizePath(namespace)}/${sanitizeStart(pathName)}`;
} else {

View File

@@ -68,8 +68,8 @@
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
/>
{{/if}}
{{#if this.permissionReadFailed}}
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} />
{{#if this.permissionBanner}}
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} @failType={{this.permissionBanner}} />
{{/if}}
</div>
<div class="global-flash">

View File

@@ -7,35 +7,51 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { click, render } from '@ember/test-helpers';
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) {
setupRenderingTest(hooks);
test('it renders correctly by default', async function (assert) {
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__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-banner] .hds-alert__title').hasText(TEXT.titleReadFail);
assert.dom('[data-test-resultant-acl-banner] .hds-alert__description').hasText(TEXT.messageReadFail);
assert.dom('[data-test-resultant-acl-reauthenticate]').doesNotExist('Does not show reauth link');
});
test('it renders correctly with set namespace', async function (assert) {
const nsService = this.owner.lookup('service:namespace');
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
.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
.dom('[data-test-resultant-acl-reauthenticate]')
.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) {

View 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();
});
});