Skip to content

Commit

Permalink
Add banner for when resultant-acl check fails (#23503)
Browse files Browse the repository at this point in the history
  • Loading branch information
hashishaw authored Oct 18, 2023
1 parent a31b029 commit 199c04f
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 74 deletions.
3 changes: 3 additions & 0 deletions changelog/23503.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: show banner when resultant-acl check fails due to permissions or wrong namespace.
```
20 changes: 20 additions & 0 deletions ui/app/components/resultant-acl-banner.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Hds::Alert @type="inline" @color="critical" data-test-resultant-acl-banner as |A|>
<A.Title>Resultant ACL check failed</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."
}}
</A.Description>
{{#if @isEnterprise}}
<A.Link::Standalone
@icon="arrow-right"
@iconPosition="trailing"
@text={{concat "Log into " this.ns " namespace"}}
@route="vault.cluster.logout"
@query={{this.queryParams}}
data-test-resultant-acl-reauthenticate
/>
{{/if}}
</Hds::Alert>
16 changes: 16 additions & 0 deletions ui/app/components/resultant-acl-banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { service } from '@ember/service';
import Component from '@glimmer/component';

export default class ResultantAclBannerComponent extends Component {
@service namespace;
@service router;

get ns() {
return this.namespace.path || 'root';
}

get queryParams() {
// Bring user back to current page after login
return { redirect_to: this.router.currentURL };
}
}
2 changes: 2 additions & 0 deletions ui/app/controllers/vault/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export default Controller.extend({
consoleOpen: alias('console.isOpen'),
activeCluster: alias('auth.activeCluster'),

permissionReadFailed: alias('permissions.readFailed'),

actions: {
toggleConsole() {
this.toggleProperty('consoleOpen');
Expand Down
9 changes: 5 additions & 4 deletions ui/app/routes/vault/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import getStorage from '../../lib/token-storage';
import localStorage from 'vault/lib/local-storage';
import ClusterRoute from 'vault/mixins/cluster-route';
import ModelBoundaryRoute from 'vault/mixins/model-boundary-route';
import { assert } from '@ember/debug';

const POLL_INTERVAL_MS = 10000;

Expand Down Expand Up @@ -55,10 +56,10 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
let namespace = params.namespaceQueryParam;
const currentTokenName = this.auth.get('currentTokenName');
const managedRoot = this.featureFlagService.managedNamespaceRoot;
if (managedRoot && this.version.isOSS) {
// eslint-disable-next-line no-console
console.error('Cannot use Cloud Admin Namespace flag with OSS Vault');
}
assert(
'Cannot use VAULT_CLOUD_ADMIN_NAMESPACE flag with non-enterprise Vault version',
!(managedRoot && this.version.isOSS)
);
if (!namespace && currentTokenName && !Ember.testing) {
// if no namespace queryParam and user authenticated,
// use user's root namespace to redirect to properly param'd url
Expand Down
4 changes: 4 additions & 0 deletions ui/app/services/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default Service.extend({
exactPaths: null,
globPaths: null,
canViewAll: null,
readFailed: false,
store: service(),
auth: service(),
namespace: service(),
Expand All @@ -80,19 +81,22 @@ 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);
}
}),

setPaths(resp) {
this.set('exactPaths', resp.data.exact_paths);
this.set('globPaths', resp.data.glob_paths);
this.set('canViewAll', resp.data.root);
this.set('readFailed', false);
},

reset() {
this.set('exactPaths', null);
this.set('globPaths', null);
this.set('canViewAll', null);
this.set('readFailed', false);
},

hasNavPermission(navItem, routeParams, requireAll) {
Expand Down
18 changes: 18 additions & 0 deletions ui/app/styles/components/cluster-banners.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

.cluster-banners-wrapper {
width: 100%;
max-width: 1344px;
margin: 0 auto;
padding: 0 1.5rem;

> div {
margin-top: $spacing-l;
&:last-of-type {
margin-bottom: $spacing-l;
}
}
}
11 changes: 0 additions & 11 deletions ui/app/styles/components/license-banners.scss

This file was deleted.

2 changes: 1 addition & 1 deletion ui/app/styles/core.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
@import './components/box-label';
@import './components/box-radio';
@import './components/calendar-widget';
@import './components/cluster-banners';
@import './components/codemirror';
@import './components/confirm';
@import './components/console-ui-panel';
Expand All @@ -74,7 +75,6 @@
@import './components/info-table-row';
@import './components/kmip-role-edit';
@import './components/known-secondaries-card.scss';
@import './components/license-banners';
@import './components/linked-block';
@import './components/list-item-row';
@import './components/loader';
Expand Down
98 changes: 47 additions & 51 deletions ui/app/templates/components/license-banners.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,52 @@
~}}

{{#if (and this.licenseExpired (not this.expiredDismissed))}}
<div class="license-banner-wrapper">
<Hds::Alert
@type="inline"
@color="critical"
@onDismiss={{fn this.dismissBanner "expired"}}
data-test-license-banner-expired
as |A|
>
<A.Title>License expired</A.Title>
<A.Description>
Your Vault license expired on
{{date-format @expiry "MMM d, yyyy"}}. Add a new license to your configuration and restart Vault.
</A.Description>
<A.Description class="has-top-margin-xs">
<DocLink @path="/vault/tutorials/enterprise/hashicorp-enterprise-license">
Read documentation
<Icon @name="learn-link" />
</DocLink>
</A.Description>
</Hds::Alert>
</div>
<Hds::Alert
@type="inline"
@color="critical"
@onDismiss={{fn this.dismissBanner "expired"}}
data-test-license-banner-expired
as |A|
>
<A.Title>License expired</A.Title>
<A.Description>
Your Vault license expired on
{{date-format @expiry "MMM d, yyyy"}}. Add a new license to your configuration and restart Vault.
</A.Description>
<A.Description class="has-top-margin-xs">
<DocLink @path="/vault/tutorials/enterprise/hashicorp-enterprise-license">
Read documentation
<Icon @name="learn-link" />
</DocLink>
</A.Description>
</Hds::Alert>
{{else if (and (lte this.licenseExpiringInDays 30) (not this.warningDismissed))}}
<div class="license-banner-wrapper">
<Hds::Alert
@type="inline"
@color="warning"
@onDismiss={{fn this.dismissBanner "warning"}}
data-test-license-banner-warning
as |A|
>
<A.Title>Vault license expiring</A.Title>
<A.Description>
Your Vault license will expire in
{{this.licenseExpiringInDays}}
days at
{{date-format @expiry "hh:mm:ss a"}}
on
{{date-format @expiry "MMM d, yyyy"}}.
{{if
@autoloaded
"Add a new license to your configuration."
"Keep in mind that your next license will need to be autoloaded."
}}
</A.Description>
<A.Description class="has-top-margin-xs">
<DocLink @path="/vault/tutorials/enterprise/hashicorp-enterprise-license">
Read documentation
<Icon @name="learn-link" />
</DocLink>
</A.Description>
</Hds::Alert>
</div>
<Hds::Alert
@type="inline"
@color="warning"
@onDismiss={{fn this.dismissBanner "warning"}}
data-test-license-banner-warning
as |A|
>
<A.Title>Vault license expiring</A.Title>
<A.Description>
Your Vault license will expire in
{{this.licenseExpiringInDays}}
days at
{{date-format @expiry "hh:mm:ss a"}}
on
{{date-format @expiry "MMM d, yyyy"}}.
{{if
@autoloaded
"Add a new license to your configuration."
"Keep in mind that your next license will need to be autoloaded."
}}
</A.Description>
<A.Description class="has-top-margin-xs">
<DocLink @path="/vault/tutorials/enterprise/hashicorp-enterprise-license">
Read documentation
<Icon @name="learn-link" />
</DocLink>
</A.Description>
</Hds::Alert>
{{/if}}
18 changes: 11 additions & 7 deletions ui/app/templates/vault/cluster.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
SPDX-License-Identifier: BUSL-1.1
~}}
<Sidebar::Nav::Cluster />
{{! Only show license banners for Enterprise }}
{{#if this.activeCluster.version.isEnterprise}}
<LicenseBanners
@expiry={{this.activeCluster.licenseExpiry}}
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
/>
{{/if}}
<div class="cluster-banners-wrapper">
{{#if this.activeCluster.version.isEnterprise}}
<LicenseBanners
@expiry={{this.activeCluster.licenseExpiry}}
@autoloaded={{eq this.activeCluster.licenseState "autoloaded"}}
/>
{{/if}}
{{#if this.permissionReadFailed}}
<ResultantAclBanner @isEnterprise={{this.activeCluster.version.isEnterprise}} />
{{/if}}
</div>
<div class="global-flash">
{{#each this.flashMessages.queue as |flash|}}
<FlashMessage data-test-flash-message={{true}} @flash={{flash}} as |customComponent flash close|>
Expand Down
18 changes: 18 additions & 0 deletions ui/tests/acceptance/cluster-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,22 @@ module('Acceptance | cluster', function (hooks) {

assert.dom('[data-test-sidebar-nav-link="Policies"]').hasAttribute('href', '/ui/vault/policies/rgp');
});

test('shows error banner if resultant-acl check fails', async function (assert) {
const login_only = `
path "auth/token/lookup-self" {
capabilities = ["read"]
},
`;
await consoleComponent.runCommands([
`write sys/policies/acl/login-only policy=${btoa(login_only)}`,
`write -field=client_token auth/token/create no_default_policy=true policies="login-only"`,
]);
const noDefaultPolicyUser = consoleComponent.lastLogOutput;
assert.dom('[data-test-resultant-acl-banner]').doesNotExist('Resultant ACL banner does not show as root');
await logout.visit();
assert.dom('[data-test-resultant-acl-banner]').doesNotExist('Does not show on login page');
await authPage.login(noDefaultPolicyUser);
assert.dom('[data-test-resultant-acl-banner]').includesText('Resultant ACL check failed');
});
});
42 changes: 42 additions & 0 deletions ui/tests/integration/components/resultant-acl-banner-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';

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-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');

await render(hbs`<ResultantAclBanner @isEnterprise={{true}} />`);

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('You do not have access to resources in this namespace.');
assert
.dom('[data-test-resultant-acl-reauthenticate]')
.hasText('Log into my-ns namespace', 'Shows reauth link with given namespace');
});

test('it renders correctly with default namespace', async function (assert) {
await render(hbs`<ResultantAclBanner @isEnterprise={{true}} />`);
assert
.dom('[data-test-resultant-acl-reauthenticate]')
.hasText('Log into root namespace', 'Shows reauth link with default namespace');
});
});

0 comments on commit 199c04f

Please sign in to comment.