mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
Add banner for when resultant-acl check fails (#23503)
This commit is contained in:
parent
a31b029cf5
commit
199c04f612
13 changed files with 187 additions and 74 deletions
3
changelog/23503.txt
Normal file
3
changelog/23503.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: show banner when resultant-acl check fails due to permissions or wrong namespace.
|
||||
```
|
||||
20
ui/app/components/resultant-acl-banner.hbs
Normal file
20
ui/app/components/resultant-acl-banner.hbs
Normal 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>
|
||||
16
ui/app/components/resultant-acl-banner.js
Normal file
16
ui/app/components/resultant-acl-banner.js
Normal 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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
18
ui/app/styles/components/cluster-banners.scss
Normal file
18
ui/app/styles/components/cluster-banners.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
@ -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|>
|
||||
|
|
|
|||
|
|
@ -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
ui/tests/integration/components/resultant-acl-banner-test.js
Normal file
42
ui/tests/integration/components/resultant-acl-banner-test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue