Skip to content

Commit

Permalink
UI: Update resultant-acl banner (#25256)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hashishaw authored Feb 7, 2024
1 parent 4283caa commit 30aa1b4
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 26 deletions.
3 changes: 3 additions & 0 deletions changelog/25256.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Do not show resultant-acl banner on namespaces a user has access to
```
3 changes: 2 additions & 1 deletion ui/app/adapters/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
8 changes: 2 additions & 6 deletions ui/app/components/resultant-acl-banner.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions ui/app/components/resultant-acl-banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.";
}
}
2 changes: 1 addition & 1 deletion ui/app/controllers/vault/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
35 changes: 28 additions & 7 deletions ui/app/services/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions ui/app/templates/vault/cluster.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
34 changes: 25 additions & 9 deletions ui/tests/integration/components/resultant-acl-banner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
37 changes: 37 additions & 0 deletions ui/tests/unit/adapters/permissions-test.js
Original file line number Diff line number Diff line change
@@ -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();
});
});

0 comments on commit 30aa1b4

Please sign in to comment.