mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
Add Policy-based Navigation (#5967)
* add permissions service * start template helper * match prefixed paths * gate sidebar links * land on first page user has access to * show nav when user first logs in * clear paths when user logs out * add tests * implement feedback * show all nav items if no policy is found * update onboarding wizard * fix some unrelated tests * add support for namespaces * gate wizard * unstage package and lockfile
This commit is contained in:
parent
b436df67e9
commit
20deed3a3d
11
ui/app/adapters/permissions.js
Normal file
11
ui/app/adapters/permissions.js
Normal file
@ -0,0 +1,11 @@
|
||||
import ApplicationAdapter from './application';
|
||||
|
||||
export default ApplicationAdapter.extend({
|
||||
query() {
|
||||
return this.ajax(this.urlForQuery(), 'GET');
|
||||
},
|
||||
|
||||
urlForQuery() {
|
||||
return this.buildURL() + '/internal/ui/resultant-acl';
|
||||
},
|
||||
});
|
||||
@ -158,6 +158,9 @@ export default Component.extend(DEFAULTS, {
|
||||
|
||||
handleError(e) {
|
||||
this.set('loading', false);
|
||||
if (!e.errors) {
|
||||
return e;
|
||||
}
|
||||
let errors = e.errors.map(error => {
|
||||
if (error.detail) {
|
||||
return error.detail;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
'data-test-navheader': true,
|
||||
classNameBindings: 'consoleFullscreen:panel-fullscreen',
|
||||
|
||||
@ -44,6 +44,7 @@ export default Component.extend({
|
||||
steps: ['Enabling a secrets engine', 'Adding a secret'],
|
||||
selected: false,
|
||||
show: true,
|
||||
permission: 'secrets',
|
||||
},
|
||||
{
|
||||
key: 'authentication',
|
||||
@ -51,6 +52,7 @@ export default Component.extend({
|
||||
steps: ['Enabling an auth method', 'Managing your auth method'],
|
||||
selected: false,
|
||||
show: true,
|
||||
permission: 'access',
|
||||
},
|
||||
{
|
||||
key: 'policies',
|
||||
@ -63,6 +65,7 @@ export default Component.extend({
|
||||
],
|
||||
selected: false,
|
||||
show: true,
|
||||
permission: 'policies',
|
||||
},
|
||||
{
|
||||
key: 'replication',
|
||||
@ -70,6 +73,7 @@ export default Component.extend({
|
||||
steps: ['Setting up replication', 'Your cluster information'],
|
||||
selected: false,
|
||||
show: true,
|
||||
permission: 'status',
|
||||
},
|
||||
{
|
||||
key: 'tools',
|
||||
@ -77,6 +81,7 @@ export default Component.extend({
|
||||
steps: ['Wrapping data', 'Lookup wrapped data', 'Rewrapping your data', 'Unwrapping your data'],
|
||||
selected: false,
|
||||
show: true,
|
||||
permission: 'tools',
|
||||
},
|
||||
];
|
||||
}),
|
||||
|
||||
@ -7,6 +7,7 @@ export default Controller.extend({
|
||||
store: service(),
|
||||
media: service(),
|
||||
router: service(),
|
||||
permissions: service(),
|
||||
namespaceService: service('namespace'),
|
||||
|
||||
vaultVersion: service('version'),
|
||||
|
||||
10
ui/app/helpers/has-permission.js
Normal file
10
ui/app/helpers/has-permission.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
permissions: service(),
|
||||
compute([navItem], { routeParams }) {
|
||||
let permissions = this.permissions;
|
||||
return permissions.hasNavPermission(navItem, routeParams);
|
||||
},
|
||||
});
|
||||
10
ui/app/helpers/route-params-for.js
Normal file
10
ui/app/helpers/route-params-for.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Helper.extend({
|
||||
permissions: service(),
|
||||
compute([navItem]) {
|
||||
let permissions = this.permissions;
|
||||
return permissions.navPathParams(navItem);
|
||||
},
|
||||
});
|
||||
@ -13,6 +13,7 @@ const POLL_INTERVAL_MS = 10000;
|
||||
export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
||||
namespaceService: service('namespace'),
|
||||
version: service(),
|
||||
permissions: service(),
|
||||
store: service(),
|
||||
auth: service(),
|
||||
currentCluster: service(),
|
||||
@ -58,6 +59,7 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
||||
const id = this.getClusterId(params);
|
||||
if (id) {
|
||||
this.get('auth').setCluster(id);
|
||||
this.get('permissions').getPaths.perform();
|
||||
return this.get('version').fetchFeatures();
|
||||
} else {
|
||||
return reject({ httpStatus: 404, message: 'not found', path: params.cluster_name });
|
||||
|
||||
@ -8,6 +8,7 @@ export default Route.extend(ModelBoundaryRoute, {
|
||||
controlGroup: service(),
|
||||
flashMessages: service(),
|
||||
console: service(),
|
||||
permissions: service(),
|
||||
|
||||
modelTypes: computed(function() {
|
||||
return ['secret', 'secret-engine'];
|
||||
@ -21,5 +22,6 @@ export default Route.extend(ModelBoundaryRoute, {
|
||||
this.clearModelCache();
|
||||
this.replaceWith('vault.cluster');
|
||||
this.get('flashMessages').clearMessages();
|
||||
this.get('permissions').reset();
|
||||
},
|
||||
});
|
||||
|
||||
@ -18,6 +18,7 @@ const BACKENDS = supportedAuthBackends();
|
||||
export { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX };
|
||||
|
||||
export default Service.extend({
|
||||
permissions: service(),
|
||||
namespace: service(),
|
||||
IDLE_TIMEOUT: 3 * 60e3,
|
||||
expirationCalcTS: null,
|
||||
@ -308,7 +309,15 @@ export default Service.extend({
|
||||
const adapter = this.clusterAdapter();
|
||||
|
||||
return adapter.authenticate(options).then(resp => {
|
||||
return this.persistAuthData(options, resp.auth || resp.data, this.get('namespace.path'));
|
||||
return this.persistAuthData(options, resp.auth || resp.data, this.get('namespace.path')).then(
|
||||
authData => {
|
||||
return this.get('permissions')
|
||||
.getPaths.perform()
|
||||
.then(() => {
|
||||
return authData;
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
141
ui/app/services/permissions.js
Normal file
141
ui/app/services/permissions.js
Normal file
@ -0,0 +1,141 @@
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
const API_PATHS = {
|
||||
secrets: { engine: 'cubbyhole/' },
|
||||
access: {
|
||||
methods: 'sys/auth',
|
||||
entities: 'identity/entities',
|
||||
groups: 'identity/groups',
|
||||
leases: 'sys/leases/lookup',
|
||||
namespaces: 'sys/namespaces',
|
||||
'control-groups': 'sys/control-group/',
|
||||
},
|
||||
policies: {
|
||||
acl: 'sys/policies/acl',
|
||||
rgp: 'sys/policies/rgp',
|
||||
egp: 'sys/policies/egp',
|
||||
},
|
||||
tools: {
|
||||
wrap: 'sys/wrapping/wrap',
|
||||
lookup: 'sys/wrapping/lookup',
|
||||
unwrap: 'sys/wrapping/unwrap',
|
||||
rewrap: 'sys/wrapping/rewrap',
|
||||
random: 'sys/tools/random',
|
||||
hash: 'sys/tools/hash',
|
||||
},
|
||||
status: {
|
||||
replication: 'sys/replication',
|
||||
license: 'sys/license',
|
||||
seal: 'sys/seal',
|
||||
},
|
||||
};
|
||||
|
||||
const API_PATHS_TO_ROUTE_PARAMS = {
|
||||
'sys/auth': ['vault.cluster.access.methods'],
|
||||
'identity/entities': ['vault.cluster.access.identity', 'entities'],
|
||||
'identity/groups': ['vault.cluster.access.identity', 'groups'],
|
||||
'sys/leases/lookup': ['vault.cluster.access.leases'],
|
||||
'sys/namespaces': ['vault.cluster.access.namespaces'],
|
||||
'sys/control-group/': ['vault.cluster.access.control-groups'],
|
||||
};
|
||||
|
||||
/*
|
||||
The Permissions service is used to gate top navigation and sidebar items. It fetches
|
||||
a users' policy from the resultant-acl endpoint and stores their allowed exact and glob
|
||||
paths as state. It also has methods for checking whether a user has permission for a given
|
||||
path.
|
||||
*/
|
||||
export default Service.extend({
|
||||
exactPaths: null,
|
||||
globPaths: null,
|
||||
canViewAll: null,
|
||||
store: service(),
|
||||
auth: service(),
|
||||
namespace: service(),
|
||||
|
||||
getPaths: task(function*() {
|
||||
if (this.paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let resp = yield this.get('store')
|
||||
.adapterFor('permissions')
|
||||
.query();
|
||||
this.setPaths(resp);
|
||||
return;
|
||||
} catch (err) {
|
||||
// If no policy can be found, default to showing all nav items.
|
||||
this.set('canViewAll', true);
|
||||
}
|
||||
}),
|
||||
|
||||
setPaths(resp) {
|
||||
this.set('exactPaths', resp.data.exact_paths);
|
||||
this.set('globPaths', resp.data.glob_paths);
|
||||
this.set('canViewAll', resp.data.root);
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.set('exactPaths', null);
|
||||
this.set('globPaths', null);
|
||||
this.set('canViewAll', null);
|
||||
},
|
||||
|
||||
hasNavPermission(navItem, routeParams) {
|
||||
if (routeParams) {
|
||||
return this.hasPermission(API_PATHS[navItem][routeParams]);
|
||||
}
|
||||
return Object.values(API_PATHS[navItem]).some(path => this.hasPermission(path));
|
||||
},
|
||||
|
||||
navPathParams(navItem) {
|
||||
const path = Object.values(API_PATHS[navItem]).find(path => this.hasPermission(path));
|
||||
if (['policies', 'tools'].includes(navItem)) {
|
||||
return path.split('/').lastObject;
|
||||
}
|
||||
|
||||
return API_PATHS_TO_ROUTE_PARAMS[path];
|
||||
},
|
||||
|
||||
pathNameWithNamespace(pathName) {
|
||||
const namespace = this.get('namespace').path;
|
||||
if (namespace) {
|
||||
return `${namespace}/${pathName}`;
|
||||
} else {
|
||||
return pathName;
|
||||
}
|
||||
},
|
||||
|
||||
hasPermission(pathName) {
|
||||
const path = this.pathNameWithNamespace(pathName);
|
||||
|
||||
if (this.canViewAll || this.hasMatchingExactPath(path) || this.hasMatchingGlobPath(path)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
hasMatchingExactPath(pathName) {
|
||||
const exactPaths = this.get('exactPaths');
|
||||
if (exactPaths) {
|
||||
const prefix = Object.keys(exactPaths).find(path => path.startsWith(pathName));
|
||||
return prefix && !this.isDenied(exactPaths[prefix]);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
hasMatchingGlobPath(pathName) {
|
||||
const globPaths = this.get('globPaths');
|
||||
if (globPaths) {
|
||||
const matchingPath = Object.keys(globPaths).find(k => pathName.includes(k));
|
||||
return (matchingPath && !this.isDenied(globPaths[matchingPath])) || globPaths.hasOwnProperty('');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
isDenied(path) {
|
||||
return path.capabilities.includes('deny');
|
||||
},
|
||||
});
|
||||
@ -4,6 +4,12 @@
|
||||
color: $grey;
|
||||
}
|
||||
|
||||
.access-information {
|
||||
display: flex;
|
||||
padding: $size-8 0px;
|
||||
font-size: $size-8;
|
||||
}
|
||||
|
||||
.feature-box {
|
||||
box-shadow: $box-shadow;
|
||||
border-radius: $radius;
|
||||
|
||||
@ -2,13 +2,17 @@
|
||||
<h2 class="title is-6">
|
||||
Choosing where to go
|
||||
</h2>
|
||||
<p>You did it! You now have access to your Vault and can start entering your data. We can help you get started with any of the options below</p>
|
||||
<p>You did it! You now have access to your Vault and can start entering your data. We can help you get started with any of the options below.</p>
|
||||
<div class="access-information">
|
||||
<ICon @glyph="information-circled" class="has-text-info"/>
|
||||
<p>Vault only shows links to pages that you have access to based on your policies. Contact your administrator if you need access changes.</p>
|
||||
</div>
|
||||
{{#if (or (has-feature "Performance Replication") (has-feature "DR Replication")) }}
|
||||
{{/if}}
|
||||
<h3 class="feature-header">Walk me through setting up:</h3>
|
||||
<form id="features-form" class="feature-selection" {{action "saveFeatures" on="submit"}}>
|
||||
{{#each allFeatures as |feature|}}
|
||||
{{#if feature.show}}
|
||||
{{#if (and feature.show (has-permission feature.permission))}}
|
||||
<div class="feature-box {{if feature.selected 'is-active'}}">
|
||||
<div class="b-checkbox">
|
||||
<input
|
||||
|
||||
@ -1,117 +1,130 @@
|
||||
<div class="popup-menu-content">
|
||||
<div class="box">
|
||||
{{#if (and activeCluster.unsealed auth.currentToken)}}
|
||||
<nav class="menu">
|
||||
<p class="menu-label">Replication</p>
|
||||
<ul>
|
||||
{{#if cluster.anyReplicationEnabled}}
|
||||
<li>
|
||||
{{#link-to
|
||||
"vault.cluster.replication.mode.index"
|
||||
"dr"
|
||||
disabled=(not currentToken)
|
||||
invokeAction=(action onLinkClick)
|
||||
}}
|
||||
{{replication-mode-summary
|
||||
mode="dr"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#if (has-feature "Performance Replication")}}
|
||||
{{#if (has-permission 'status' routeParams='replication')}}
|
||||
<nav class="menu">
|
||||
<p class="menu-label">Replication</p>
|
||||
<ul>
|
||||
{{#if cluster.anyReplicationEnabled}}
|
||||
<li>
|
||||
{{#link-to
|
||||
"vault.cluster.replication.mode.index"
|
||||
"performance"
|
||||
"dr"
|
||||
disabled=(not currentToken)
|
||||
invokeAction=(action onLinkClick)
|
||||
}}
|
||||
{{replication-mode-summary
|
||||
mode="dr"
|
||||
display='menu'
|
||||
cluster=cluster
|
||||
}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#if (has-feature "Performance Replication")}}
|
||||
{{#link-to
|
||||
"vault.cluster.replication.mode.index"
|
||||
"performance"
|
||||
disabled=(not currentToken)
|
||||
invokeAction=(action onLinkClick)
|
||||
}}
|
||||
{{replication-mode-summary
|
||||
mode="performance"
|
||||
display="menu"
|
||||
cluster=cluster
|
||||
tagName="span"
|
||||
}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
{{replication-mode-summary
|
||||
mode="performance"
|
||||
display="menu"
|
||||
cluster=cluster
|
||||
tagName="span"
|
||||
class="menu-item"
|
||||
}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{else if version.isOSS}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.replication"}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Learn More</span>
|
||||
<ICon @glyph="edition-enterprise" @size="16" @class="level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
{{replication-mode-summary
|
||||
mode="performance"
|
||||
display="menu"
|
||||
cluster=cluster
|
||||
class="menu-item"
|
||||
</li>
|
||||
{{else}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.replication"
|
||||
invokeAction=(action onLinkClick)
|
||||
}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{else if version.isOSS}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.replication"}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Learn More</span>
|
||||
<ICon @glyph="edition-enterprise" @size="16" @class="level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.replication"
|
||||
invokeAction=(action onLinkClick)
|
||||
}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Enable</span>
|
||||
<ICon @glyph="neutral-circled-outline" @size="16" @class="has-text-grey-light level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
<hr/>
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Enable</span>
|
||||
<ICon @glyph="neutral-circled-outline" @size="16" @class="has-text-grey-light level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
<hr/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#unless version.isOSS}}
|
||||
{{#if (has-permission 'status' routeParams='license')}}
|
||||
<nav class="menu">
|
||||
<div class="menu-label">
|
||||
License
|
||||
</div>
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
{{#link-to "vault.cluster.license" activeCluster.name invokeAction=onLinkClick}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">See Details</span>
|
||||
<ICon @glyph="chevron-right" @size="12" @class="has-text-grey-light level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<hr/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
<nav class="menu">
|
||||
<div class="menu-label">
|
||||
License
|
||||
Seal Status
|
||||
</div>
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
{{#link-to "vault.cluster.license" activeCluster.name invokeAction=onLinkClick}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">See Details</span>
|
||||
<ICon @glyph="chevron-right" @size="12" @class="has-text-grey-light level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
{{#if activeCluster.unsealed}}
|
||||
{{#if (has-permission 'status' routeParams='seal')}}
|
||||
{{#link-to 'vault.cluster.settings.seal' cluster.name
|
||||
class="level is-mobile"
|
||||
invokeAction=(action (queue (action onLinkClick) (action d.actions.close)))
|
||||
}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Unsealed</span>
|
||||
<ICon @glyph="checkmark-circled-outline" @size="16" @class="has-text-success level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
<span class="menu-item">
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Unsealed</span>
|
||||
<ICon @glyph="checkmark-circled-outline" @size="16" @class="has-text-success level-right" />
|
||||
</div>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="menu-item">
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left has-text-danger">Sealed</span>
|
||||
<ICon @glyph="close-circled-outline" @size="16" @class="has-text-danger level-right" />
|
||||
</div>
|
||||
</span>
|
||||
{{/if}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<hr/>
|
||||
{{/unless}}
|
||||
<nav class="menu">
|
||||
<div class="menu-label">
|
||||
Seal Status
|
||||
</div>
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
{{#if activeCluster.unsealed}}
|
||||
{{#link-to 'vault.cluster.settings.seal' cluster.name
|
||||
class="level is-mobile"
|
||||
invokeAction=(action (queue (action onLinkClick) (action d.actions.close)))
|
||||
}}
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left">Unsealed</span>
|
||||
<ICon @glyph="checkmark-circled-outline" @size="16" @class="has-text-success level-right" />
|
||||
</div>
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
<span class="menu-item">
|
||||
<div class="level is-mobile">
|
||||
<span class="level-left has-text-danger">Sealed</span>
|
||||
<ICon @glyph="close-circled-outline" @size="16" @class="has-text-danger level-right" />
|
||||
</div>
|
||||
</span>
|
||||
{{/if}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,43 +17,54 @@
|
||||
</NamespacePicker>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="{{if (is-active-route 'vault.cluster.secrets') 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.secrets"
|
||||
current-when="vault.cluster.secrets vault.cluster.settings.mount-secret-backend vault.cluster.settings.configure-secret-backend"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
}}
|
||||
Secrets
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li class="{{if (is-active-route 'vault.cluster.access') 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.access"
|
||||
{{#if (has-permission 'secrets')}}
|
||||
<li class="{{if (is-active-route 'vault.cluster.secrets') 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.secrets"
|
||||
current-when="vault.cluster.secrets vault.cluster.settings.mount-secret-backend vault.cluster.settings.configure-secret-backend"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
data-test-navbar-item='secrets'
|
||||
}}
|
||||
Secrets
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission 'access')}}
|
||||
<li class="{{if (is-active-route 'vault.cluster.access') 'is-active'}}">
|
||||
{{#link-to
|
||||
params=(route-params-for 'access')
|
||||
current-when="vault.cluster.access vault.cluster.settings.auth"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
data-test-navbar-item='access'
|
||||
}}
|
||||
Access
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission 'policies')}}
|
||||
<li class="{{if (is-active-route (array 'vault.cluster.policies' 'vault.cluster.policy')) 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.policies"
|
||||
(route-params-for 'policies')
|
||||
current-when="vault.cluster.policies vault.cluster.policy"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
data-test-navbar-item='policies'
|
||||
}}
|
||||
Access
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li class="{{if (is-active-route (array 'vault.cluster.policies' 'vault.cluster.policy')) 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.policies"
|
||||
"acl"
|
||||
current-when="vault.cluster.policies vault.cluster.policy"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
}}
|
||||
Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li class="{{if (is-active-route 'vault.cluster.tools') 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.tools.tool"
|
||||
"wrap"
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
}}
|
||||
Tools
|
||||
{{/link-to}}
|
||||
</li>
|
||||
Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission 'tools')}}
|
||||
<li class="{{if (is-active-route 'vault.cluster.tools') 'is-active'}}">
|
||||
{{#link-to
|
||||
"vault.cluster.tools.tool"
|
||||
(route-params-for 'tools')
|
||||
invokeAction=(action Nav.closeDrawer)
|
||||
}}
|
||||
Tools
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</Nav.main>
|
||||
<Nav.items>
|
||||
|
||||
@ -1,47 +1,59 @@
|
||||
<div class="columns">
|
||||
{{#menu-sidebar title="Access" class="is-3" data-test-sidebar=true}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.methods" data-test-link=true current-when="vault.cluster.access.methods vault.cluster.access.method"}}
|
||||
Auth Methods
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.identity" "entities" data-test-link=true }}
|
||||
Entities
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.identity" "groups" data-test-link=true }}
|
||||
Groups
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.leases" data-test-link=true}}
|
||||
Leases
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.namespaces" data-test-link=true }}
|
||||
Namespaces
|
||||
{{#unless (has-feature "Namespaces")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.control-groups" data-test-link=true current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure"}}
|
||||
Control Groups
|
||||
{{#unless (has-feature "Control Groups")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if (has-permission "access" routeParams="methods")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.methods" data-test-link=true current-when="vault.cluster.access.methods vault.cluster.access.method"}}
|
||||
Auth Methods
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="entities")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.identity" "entities" data-test-link=true }}
|
||||
Entities
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="groups")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.identity" "groups" data-test-link=true }}
|
||||
Groups
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="leases")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.leases" data-test-link=true}}
|
||||
Leases
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="namespaces")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.namespaces" data-test-link=true }}
|
||||
Namespaces
|
||||
{{#unless (has-feature "Namespaces")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access" routeParams="control-groups")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.access.control-groups" data-test-link=true current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure"}}
|
||||
Control Groups
|
||||
{{#unless (has-feature "Control Groups")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/menu-sidebar}}
|
||||
<div class="column is-9">
|
||||
{{outlet}}
|
||||
|
||||
@ -1,36 +1,42 @@
|
||||
<div class="columns">
|
||||
{{#menu-sidebar title="Policies" class="is-3" data-test-sidebar=true}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "acl" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "acl") "is-active")}}
|
||||
ACL Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "rgp" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "rgp") "is-active")}}
|
||||
Role Governing Policies
|
||||
{{#if (has-permission "policies" routeParams="acl")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "acl" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "acl") "is-active")}}
|
||||
ACL Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="rgp")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "rgp" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "rgp") "is-active")}}
|
||||
Role Governing Policies
|
||||
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "egp" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "egp") "is-active")}}
|
||||
Endpoint Governing Policies
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="egp")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "egp" data-test-link=true class=(if (is-active-route "vault.cluster.policies" "egp") "is-active")}}
|
||||
Endpoint Governing Policies
|
||||
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/menu-sidebar}}
|
||||
<div class="column is-9">
|
||||
{{outlet}}
|
||||
|
||||
@ -1,34 +1,40 @@
|
||||
<div class="columns">
|
||||
{{#menu-sidebar title="Policies" class="is-3" data-test-sidebar=true}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "acl" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "acl") "is-active")}}
|
||||
ACL Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "rgp" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "rgp") "is-active")}}
|
||||
Role Governing Policies
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "egp" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "egp") "is-active")}}
|
||||
Endpoint Governing Policies
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if (has-permission "policies" routeParams="acl")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "acl" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "acl") "is-active")}}
|
||||
ACL Policies
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="rgp")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "rgp" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "rgp") "is-active")}}
|
||||
Role Governing Policies
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (has-permission "policies" routeParams="egp")}}
|
||||
<li>
|
||||
{{#link-to "vault.cluster.policies" "egp" data-test-link=true class=(if (is-active-route "vault.cluster.policy" "egp") "is-active")}}
|
||||
Endpoint Governing Policies
|
||||
{{#unless (has-feature "Sentinel")}}
|
||||
{{#if (is-version "OSS")}}
|
||||
{{edition-badge edition="Enterprise"}}
|
||||
{{else}}
|
||||
{{edition-badge edition="Premium"}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/menu-sidebar}}
|
||||
<div class="column is-9">
|
||||
{{outlet}}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
<div class="columns">
|
||||
{{#menu-sidebar title="Tools" class="is-3"}}
|
||||
{{#each (tools-actions) as |supportedAction|}}
|
||||
<li>
|
||||
{{#link-to 'vault.cluster.tools.tool' supportedAction refreshModel=true
|
||||
class="(if (eq supportedAction selectedAction) 'is-active')"
|
||||
data-test-tools-action-link=supportedAction
|
||||
}}
|
||||
{{capitalize supportedAction}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{#if (has-permission "tools" routeParams=supportedAction)}}
|
||||
<li>
|
||||
{{#link-to 'vault.cluster.tools.tool' supportedAction refreshModel=true
|
||||
class="(if (eq supportedAction selectedAction) 'is-active')"
|
||||
data-test-tools-action-link=supportedAction
|
||||
}}
|
||||
{{capitalize supportedAction}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/menu-sidebar}}
|
||||
<div class="column is-9">
|
||||
|
||||
@ -62,7 +62,7 @@ module.exports = function(environment) {
|
||||
ENV.flashMessageDefaults.timeout = 50;
|
||||
}
|
||||
if (environment !== 'production') {
|
||||
ENV.APP.DEFAULT_PAGE_SIZE = 5;
|
||||
ENV.APP.DEFAULT_PAGE_SIZE = 15;
|
||||
ENV.contentSecurityPolicyHeader = 'Content-Security-Policy';
|
||||
ENV.contentSecurityPolicyMeta = true;
|
||||
ENV.contentSecurityPolicy = {
|
||||
|
||||
71
ui/tests/acceptance/cluster-test.js
Normal file
71
ui/tests/acceptance/cluster-test.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import logout from 'vault/tests/pages/logout';
|
||||
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||
|
||||
const consoleComponent = create(consoleClass);
|
||||
|
||||
const tokenWithPolicy = async function(name, policy) {
|
||||
await consoleComponent.runCommands([
|
||||
`write sys/policies/acl/${name} policy=${policy}`,
|
||||
`write -field=client_token auth/token/create policies=${name}`,
|
||||
]);
|
||||
|
||||
return consoleComponent.lastLogOutput;
|
||||
};
|
||||
|
||||
module('Acceptance | cluster', function(hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(async function() {
|
||||
await logout.visit();
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
test('hides nav item if user does not have permission', async function(assert) {
|
||||
const deny_policies_policy = `'
|
||||
path "sys/policies/*" {
|
||||
capabilities = ["deny"]
|
||||
},
|
||||
'`;
|
||||
|
||||
const userToken = await tokenWithPolicy('hide-policies-nav', deny_policies_policy);
|
||||
await logout.visit();
|
||||
await authPage.login(userToken);
|
||||
|
||||
assert.dom('[data-test-navbar-item=policies]').doesNotExist();
|
||||
await logout.visit();
|
||||
});
|
||||
|
||||
test('shows nav item if user does have permission', async function(assert) {
|
||||
const read_secrets_policy = `'
|
||||
path "cubbyhole/" {
|
||||
capabilities = ["read"]
|
||||
},
|
||||
'`;
|
||||
|
||||
const userToken = await tokenWithPolicy('show-secrets-nav', read_secrets_policy);
|
||||
await logout.visit();
|
||||
await authPage.login(userToken);
|
||||
|
||||
assert.dom('[data-test-navbar-item=secrets]').exists();
|
||||
await logout.visit();
|
||||
});
|
||||
|
||||
test('nav item links to first route that user has access to', async function(assert) {
|
||||
const read_rgp_policy = `'
|
||||
path "sys/policies/rgp" {
|
||||
capabilities = ["read"]
|
||||
},
|
||||
'`;
|
||||
|
||||
const userToken = await tokenWithPolicy('show-policies-nav', read_rgp_policy);
|
||||
await logout.visit();
|
||||
await authPage.login(userToken);
|
||||
|
||||
assert.dom('[data-test-navbar-item=policies]').hasAttribute('href', '/ui/vault/policies/rgp');
|
||||
await logout.visit();
|
||||
});
|
||||
});
|
||||
@ -48,18 +48,17 @@ module('Integration | Component | auth form', function(hooks) {
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
this.owner.lookup('service:csp-event').attach();
|
||||
component.setContext(this);
|
||||
this.owner.register('service:router', routerService);
|
||||
this.router = this.owner.lookup('service:router');
|
||||
});
|
||||
|
||||
hooks.afterEach(function() {
|
||||
this.owner.lookup('service:csp-event').remove();
|
||||
component.removeContext();
|
||||
});
|
||||
|
||||
const CSP_ERR_TEXT = `Error This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`;
|
||||
test('it renders error on CSP violation', async function(assert) {
|
||||
this.owner.unregister('service:auth');
|
||||
this.owner.register('service:auth', authService);
|
||||
this.auth = this.owner.lookup('service:auth');
|
||||
this.set('cluster', EmberObject.create({ standby: true }));
|
||||
@ -156,6 +155,7 @@ module('Integration | Component | auth form', function(hooks) {
|
||||
});
|
||||
|
||||
test('it calls authenticate with the correct path', async function(assert) {
|
||||
this.owner.unregister('service:auth');
|
||||
this.owner.register('service:auth', workingAuthService);
|
||||
this.auth = this.owner.lookup('service:auth');
|
||||
let authSpy = sinon.spy(this.get('auth'), 'authenticate');
|
||||
|
||||
162
ui/tests/unit/services/permissions-test.js
Normal file
162
ui/tests/unit/services/permissions-test.js
Normal file
@ -0,0 +1,162 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import Pretender from 'pretender';
|
||||
import Service from '@ember/service';
|
||||
|
||||
const PERMISSIONS_RESPONSE = {
|
||||
data: {
|
||||
exact_paths: {
|
||||
foo: {
|
||||
capabilities: ['read'],
|
||||
},
|
||||
'bar/bee': {
|
||||
capabilities: ['create'],
|
||||
},
|
||||
boo: {
|
||||
capabilities: ['deny'],
|
||||
},
|
||||
},
|
||||
glob_paths: {
|
||||
'baz/biz': {
|
||||
capabilities: ['read'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module('Unit | Service | permissions', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
this.server = new Pretender();
|
||||
this.server.get('/v1/sys/internal/ui/resultant-acl', () => {
|
||||
return [200, { 'Content-Type': 'application/json' }, JSON.stringify(PERMISSIONS_RESPONSE)];
|
||||
});
|
||||
});
|
||||
|
||||
hooks.afterEach(function() {
|
||||
this.server.shutdown();
|
||||
});
|
||||
|
||||
test('sets paths properly', async function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
await service.getPaths.perform();
|
||||
assert.deepEqual(service.get('exactPaths'), PERMISSIONS_RESPONSE.data.exact_paths);
|
||||
assert.deepEqual(service.get('globPaths'), PERMISSIONS_RESPONSE.data.glob_paths);
|
||||
});
|
||||
|
||||
test('returns true if a policy includes access to an exact path', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
|
||||
assert.equal(service.hasPermission('foo'), true);
|
||||
});
|
||||
|
||||
test('returns true if a paths prefix is included in the policys exact paths', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('exactPaths', PERMISSIONS_RESPONSE.data.exact_paths);
|
||||
assert.equal(service.hasPermission('bar'), true);
|
||||
});
|
||||
|
||||
test('it returns true if a policy includes access to a glob path', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
|
||||
assert.equal(service.hasPermission('baz/biz/hi'), true);
|
||||
});
|
||||
|
||||
test('it returns true if a policy includes access to the * glob path', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
const splatPath = { '': {} };
|
||||
service.set('globPaths', splatPath);
|
||||
assert.equal(service.hasPermission('hi'), true);
|
||||
});
|
||||
|
||||
test('it returns false if the matched path includes the deny capability', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('globPaths', PERMISSIONS_RESPONSE.data.glob_paths);
|
||||
assert.equal(service.hasPermission('boo'), false);
|
||||
});
|
||||
|
||||
test('it returns false if a policy does not includes access to a path', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
assert.equal(service.hasPermission('danger'), false);
|
||||
});
|
||||
|
||||
test('sets the root token', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.setPaths({ data: { root: true } });
|
||||
assert.equal(service.canViewAll, true);
|
||||
});
|
||||
|
||||
test('returns true with the root token', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('canViewAll', true);
|
||||
assert.equal(service.hasPermission('hi'), true);
|
||||
});
|
||||
|
||||
test('defaults to show all items when policy cannot be found', async function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
this.server.get('/v1/sys/internal/ui/resultant-acl', () => {
|
||||
return [403, { 'Content-Type': 'application/json' }];
|
||||
});
|
||||
await service.getPaths.perform();
|
||||
assert.equal(service.canViewAll, true);
|
||||
});
|
||||
|
||||
test('returns the first allowed nav route for policies', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
const policyPaths = {
|
||||
'sys/policies/acl': {
|
||||
capabilities: ['deny'],
|
||||
},
|
||||
'sys/policies/rgp': {
|
||||
capabilities: ['read'],
|
||||
},
|
||||
};
|
||||
service.set('exactPaths', policyPaths);
|
||||
assert.equal(service.navPathParams('policies'), 'rgp');
|
||||
});
|
||||
|
||||
test('returns the first allowed nav route for access', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
const accessPaths = {
|
||||
'sys/auth': {
|
||||
capabilities: ['deny'],
|
||||
},
|
||||
'identity/entities': {
|
||||
capabilities: ['read'],
|
||||
},
|
||||
};
|
||||
const expected = ['vault.cluster.access.identity', 'entities'];
|
||||
service.set('exactPaths', accessPaths);
|
||||
assert.deepEqual(service.navPathParams('access'), expected);
|
||||
});
|
||||
|
||||
test('hasNavPermission returns true if a policy includes access to at least one path', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
const accessPaths = {
|
||||
'sys/auth': {
|
||||
capabilities: ['deny'],
|
||||
},
|
||||
'sys/leases/lookup': {
|
||||
capabilities: ['read'],
|
||||
},
|
||||
};
|
||||
service.set('exactPaths', accessPaths);
|
||||
assert.equal(service.hasNavPermission('access', 'leases'), true);
|
||||
});
|
||||
|
||||
test('hasNavPermission returns false if a policy does not include access to any paths', function(assert) {
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
service.set('exactPaths', {});
|
||||
assert.equal(service.hasNavPermission('access'), false);
|
||||
});
|
||||
|
||||
test('appends the namespace to the path if there is one', function(assert) {
|
||||
const namespaceService = Service.extend({
|
||||
path: 'marketing',
|
||||
});
|
||||
this.owner.register('service:namespace', namespaceService);
|
||||
let service = this.owner.lookup('service:permissions');
|
||||
assert.equal(service.pathNameWithNamespace('sys/auth'), 'marketing/sys/auth');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user