Add banner for when resultant-acl check fails (#23503)

This commit is contained in:
Chelsea Shaw 2023-10-18 16:51:36 -05:00 committed by GitHub
parent a31b029cf5
commit 199c04f612
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 187 additions and 74 deletions

3
changelog/23503.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
ui: show banner when resultant-acl check fails due to permissions or wrong namespace.
```

View file

@ -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>

View file

@ -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 };
}
}

View file

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

View file

@ -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;
@ -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

View file

@ -64,6 +64,7 @@ export default Service.extend({
exactPaths: null,
globPaths: null,
canViewAll: null,
readFailed: false,
store: service(),
auth: service(),
namespace: service(),
@ -80,6 +81,7 @@ 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);
}
}),
@ -87,12 +89,14 @@ export default Service.extend({
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) {

View file

@ -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;
}
}
}

View file

@ -1,11 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
.license-banner-wrapper {
width: 100%;
max-width: 1344px;
margin: $spacing-l auto 0;
padding: 0 1.5rem;
}

View file

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

View file

@ -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}}

View file

@ -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|>

View file

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

View file

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