ui: Implement ACLs access based on ACLs (#9835)

Adds restrictions to everything within the ACLs (and nspaces) area based on your ACLs (including readonly views etc.)
This commit is contained in:
John Cowen 2021-03-11 09:29:11 +00:00 committed by GitHub
parent 63ce35a24e
commit 887d8038b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 339 additions and 65 deletions

View File

@ -1,9 +1,6 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
// ACL ability covers all of the ACL things, like tokens, policies, roles and
// auth methods and this therefore should not be deleted once we remove the on
// legacy ACLs related classes
export default class ACLAbility extends BaseAbility {
@service('env') env;
@ -13,4 +10,10 @@ export default class ACLAbility extends BaseAbility {
get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canRead;
}
get canDuplicate() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canWrite;
}
get canDelete() {
return this.env.var('CONSUL_ACLS_ENABLED') && this.item.ID !== 'anonymous' && super.canWrite;
}
}

View File

@ -0,0 +1,21 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
export default class AuthMethodAbility extends BaseAbility {
@service('env') env;
resource = 'acl';
segmented = false;
get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canRead;
}
get canCreate() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canCreate;
}
get canDelete() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canDelete;
}
}

View File

@ -11,6 +11,10 @@ export default class NspaceAbility extends BaseAbility {
return this.canCreate;
}
get canDelete() {
return this.item.Name !== 'default' && super.canDelete;
}
get canChoose() {
return this.env.var('CONSUL_NSPACES_ENABLED') && this.nspaces.length > 0;
}

View File

@ -0,0 +1,34 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
import { typeOf } from 'consul-ui/helpers/policy/typeof';
export default class PolicyAbility extends BaseAbility {
@service('env') env;
resource = 'acl';
segmented = false;
get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canRead;
}
get canWrite() {
return (
this.env.var('CONSUL_ACLS_ENABLED') &&
(typeof this.item === 'undefined' || typeOf([this.item]) !== 'policy-management') &&
super.canRead
);
}
get canCreate() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canCreate;
}
get canDelete() {
return (
this.env.var('CONSUL_ACLS_ENABLED') &&
(typeof this.item === 'undefined' || typeOf([this.item]) !== 'policy-management') &&
super.canDelete
);
}
}

View File

@ -0,0 +1,21 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
export default class RoleAbility extends BaseAbility {
@service('env') env;
resource = 'acl';
segmented = false;
get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canRead;
}
get canCreate() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canCreate;
}
get canDelete() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canDelete;
}
}

View File

@ -0,0 +1,33 @@
import BaseAbility from './base';
import { inject as service } from '@ember/service';
import { isLegacy } from 'consul-ui/helpers/token/is-legacy';
import { isAnonymous } from 'consul-ui/helpers/token/is-anonymous';
export default class TokenAbility extends BaseAbility {
@service('env') env;
resource = 'acl';
segmented = false;
get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canRead;
}
get canCreate() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.canCreate;
}
get canDelete() {
return (
this.env.var('CONSUL_ACLS_ENABLED') &&
!isAnonymous([this.item]) &&
this.item.AccessorID !== this.token.AccessorID &&
super.canDelete
);
}
get canDuplicate() {
return this.env.var('CONSUL_ACLS_ENABLED') && !isLegacy([this.item]) && super.canWrite;
}
}

View File

@ -3,6 +3,7 @@
...attributes
>
{{yield}}
{{#if (not disabled)}}
<YieldSlot @name="create">{{yield}}</YieldSlot>
<label class="type-text">
<span><YieldSlot @name="label">{{yield}}</YieldSlot></span>
@ -35,6 +36,7 @@
</PowerSelect>
</DataCollection>
</label>
{{/if}}
{{#if (gt items.length 0)}}
<YieldSlot @name="set">{{yield}}</YieldSlot>
{{else}}

View File

@ -29,7 +29,13 @@
</BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none">
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.edit' item.ID}}>Edit</a>
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.edit' item.ID}}>
{{#if (can "write acl" item=item)}}
Edit
{{else}}
View
{{/if}}
</a>
</li>
{{#if (eq item.ID token.SecretID) }}
<li role="none">
@ -87,10 +93,12 @@
</div>
</li>
{{/if}}
{{#if (can "duplicate acl" item=item)}}
<li role="none">
<button role="menuitem" tabindex="-1" type="button" data-test-clone {{action @onclone item}}>Duplicate</button>
</li>
{{# if (not-eq item.ID 'anonymous') }}
{{/if}}
{{#if (can "delete acl" item=item)}}
<li role="none" class="dangerous">
<label for={{concat confirm 'delete'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
<div role="menu">

View File

@ -29,10 +29,14 @@ as |item|>
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.nspaces.edit' item.Name}}>
<BlockSlot @name="label">
{{#if (can "write nspace" item=item)}}
Edit
{{else}}
View
{{/if}}
</BlockSlot>
</Action>
{{#if (not-eq item.Name 'default') }}
{{#if (can "delete nspace" item=item)}}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete

View File

@ -35,14 +35,14 @@ as |item|>
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.acls.policies.edit' item.ID}}>
<BlockSlot @name="label">
{{#if (eq (policy/typeof item) 'policy-management')}}
View
{{else}}
{{#if (can "write policy" item=item)}}
Edit
{{else}}
View
{{/if}}
</BlockSlot>
</Action>
{{#if (not-eq (policy/typeof item) 'policy-management')}}
{{#if (can "delete policy" item=item)}}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete

View File

@ -19,9 +19,14 @@ as |item|>
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.acls.roles.edit' item.ID}}>
<BlockSlot @name="label">
{{#if (can "write role" item=item)}}
Edit
{{else}}
View
{{/if}}
</BlockSlot>
</Action>
{{#if (can "delete role" item=item)}}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
@ -42,6 +47,7 @@ as |item|>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
</Actions>
</BlockSlot>
</ListCollection>

View File

@ -30,19 +30,25 @@ as |item|>
</dl>
</BlockSlot>
<BlockSlot @name="actions" as |Actions|>
<Actions as |Action|>
<Action data-test-edit-action @href={{href-to 'dc.acls.tokens.edit' item.AccessorID}}>
<BlockSlot @name="label">
{{#if (can "write token" item=item)}}
Edit
{{else}}
View
{{/if}}
</BlockSlot>
</Action>
{{#if (not (token/is-legacy item))}}
{{#if (can "duplicate token" item=item)}}
<Action data-test-clone-action @onclick={{action @onclone item}}>
<BlockSlot @name="label">
Duplicate
</BlockSlot>
</Action>
{{/if}}
{{/if}}
{{#if (eq item.AccessorID token.AccessorID)}}
<Action data-test-logout-action class="dangerous" @onclick={{action @onlogout item}}>
<BlockSlot @name="label">
@ -86,7 +92,9 @@ as |item|>
</BlockSlot>
</Action>
{{/if}}
{{#if (not (or (token/is-anonymous item) (eq item.AccessorID @token.AccessorID)))}}
{{#if (can "delete token" item=item token=@token)}}
<Action data-test-delete-action @onclick={{action @ondelete item}} class="dangerous">
<BlockSlot @name="label">
Delete
@ -107,7 +115,8 @@ as |item|>
</Confirmation>
</BlockSlot>
</Action>
{{/if}}
{{/if}}
</Actions>
</BlockSlot>
</ListCollection>

View File

@ -1,5 +1,8 @@
{{yield}}
<fieldset ...attributes>
<fieldset
disabled={{if (not (can "write policy" item=item)) "disabled"}}
...attributes
>
{{#yield-slot name='template'}}
{{else}}
<header>

View File

@ -1,4 +1,13 @@
<ChildSelector ...attributes @repo={{repo}} @dc={{dc}} @nspace={{nspace}} @type="policy" @placeholder="Search for policy" @items={{items}}>
<ChildSelector
@disabled={{disabled}}
@repo={{repo}}
@dc={{dc}}
@nspace={{nspace}}
@type="policy"
@placeholder="Search for policy"
@items={{items}}
...attributes
>
{{yield}}
<BlockSlot @name="label">
Apply an existing policy
@ -112,6 +121,7 @@
/>
{{/if}}
</label>
{{#if (not disabled)}}
<div>
<ConfirmationDialog @message="Are you sure you want to remove this policy from this token?">
<BlockSlot @name="action" as |confirm|>
@ -127,6 +137,7 @@
</BlockSlot>
</ConfirmationDialog>
</div>
{{/if}}
</BlockSlot>
</TabularDetails>

View File

@ -1,5 +1,9 @@
{{yield}}
<fieldset class="role-form" data-test-role-form>
<fieldset
disabled={{if (not (can "write role" item=item)) "disabled"}}
class="role-form"
data-test-role-form
>
<label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span>
<input type="text" value={{item.Name}} name="role[Name]" autofocus="autofocus" oninput={{action 'change'}} />
@ -21,6 +25,11 @@
{{#yield-slot name='policy' params=(block-params item)}}
{{yield}}
{{else}}
<PolicySelector @dc={{dc}} @nspace={{nspace}} @items={{item.Policies}} />
<PolicySelector
@disabled={{not (can "write role" item=item)}}
@dc={{dc}}
@nspace={{nspace}}
@items={{item.Policies}}
/>
{{/yield-slot}}
</fieldset>

View File

@ -59,7 +59,7 @@ as |modal|>
</BlockSlot>
</ModalDialog>
<ChildSelector @repo={{repo}} @dc={{dc}} @nspace={{nspace}} @type="role" @placeholder="Search for role" @items={{items}}>
<ChildSelector @disabled={{disabled}} @repo={{repo}} @dc={{dc}} @nspace={{nspace}} @type="role" @placeholder="Search for role" @items={{items}}>
<BlockSlot @name="label">
Apply an existing role
</BlockSlot>
@ -70,7 +70,6 @@ as |modal|>
>
<span>Create new role</span>
</label>
</BlockSlot>
<BlockSlot @name="option" as |option|>
{{option.Name}}
@ -100,8 +99,15 @@ as |modal|>
</BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none">
<a role="menuitem" tabindex="-1" href={{href-to 'dc.acls.roles.edit' item.ID}}>Edit</a>
<a role="menuitem" tabindex="-1" href={{href-to 'dc.acls.roles.edit' item.ID}}>
{{#if (can "edit role" item=item)}}
Edit
{{else}}
View
{{/if}}
</a>
</li>
{{#if (not disabled)}}
<li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Remove</label>
<div role="menu">
@ -125,6 +131,7 @@ as |modal|>
</div>
</div>
</li>
{{/if}}
</BlockSlot>
</PopoverMenu>
</BlockSlot>

View File

@ -128,42 +128,69 @@ export const routes = {
},
// ACLs
acls: {
_options: { path: '/acls' },
_options: {
path: '/acls',
abilities: ['read acls'],
},
edit: {
_options: { path: '/:id' },
},
create: {
_options: { path: '/create' },
_options: {
path: '/create',
abilities: ['create acls'],
},
},
policies: {
_options: { path: '/policies' },
_options: {
path: '/policies',
abilities: ['read policies'],
},
edit: {
_options: { path: '/:id' },
},
create: {
_options: { path: '/create' },
_options: {
path: '/create',
abilities: ['create policies'],
},
},
},
roles: {
_options: { path: '/roles' },
_options: {
path: '/roles',
abilities: ['read roles'],
},
edit: {
_options: { path: '/:id' },
},
create: {
_options: { path: '/create' },
_options: {
path: '/create',
abilities: ['create roles'],
},
},
},
tokens: {
_options: { path: '/tokens' },
_options: {
path: '/tokens',
abilities: ['read tokens'],
},
edit: {
_options: { path: '/:id' },
},
create: {
_options: { path: '/create' },
_options: {
path: '/create',
abilities: ['create tokens'],
},
},
},
'auth-methods': {
_options: { path: '/auth-methods' },
_options: {
path: '/auth-methods',
abilities: ['read auth-methods'],
},
show: {
_options: { path: '/show' },
},
@ -185,12 +212,18 @@ export const routes = {
};
if (env('CONSUL_NSPACES_ENABLED')) {
routes.dc.nspaces = {
_options: { path: '/namespaces' },
_options: {
path: '/namespaces',
abilities: ['read nspaces'],
},
edit: {
_options: { path: '/:name' },
},
create: {
_options: { path: '/create' },
_options: {
path: '/create',
abilities: ['create nspaces'],
},
},
};
routes.nspace = {

View File

@ -4,12 +4,10 @@ import CreatingRoute from 'consul-ui/mixins/creating-route';
export default class CreateRoute extends Route.extend(CreatingRoute) {
templateName = 'dc/nspaces/edit';
beforeModel() {
async beforeModel() {
// TODO: Update nspace CRUD to use Data components
// we need to skip CreatingRoute.beforeModel here
// TODO(octane): ideally we'd like to call Route.beforeModel
// but its not clear how to do that with old ember
// maybe it will be more normal with modern ember
// up until now we haven't been calling super here anyway
// so this is probably ok until we can skip a parent super
// but still call Route.beforeModel
return Route.prototype.beforeModel.apply(this, arguments);
}
}

View File

@ -16,14 +16,19 @@ export default class BaseRoute extends Route {
* Inspects a custom `abilities` array on the router for this route. Every
* abililty needs to 'pass' for the route not to throw a 403 error. Anything
* more complex then this (say ORs) should use a single ability and perform
* the OR lgic in the test for the ability. Note, this ability check happens
* the OR logic in the test for the ability. Note, this ability check happens
* before any calls to the backend for this model/route.
*/
async beforeModel() {
const abilities = get(routes, `${this.routeName}._options.abilities`) || [];
// remove any references to index as it is the same as the root routeName
const routeName = this.routeName
.split('.')
.filter(item => item !== 'index')
.join('.');
const abilities = get(routes, `${routeName}._options.abilities`) || [];
if (abilities.length > 0) {
if (!abilities.every(ability => this.permissions.can(ability))) {
throw new HTTPError(403);
throw new HTTPError('403');
}
}
}

View File

@ -11,6 +11,10 @@ const REQUIRED_PERMISSIONS = [
Resource: 'operator',
Access: 'write',
},
{
Resource: 'operator',
Access: 'read',
},
{
Resource: 'service',
Access: 'read',

View File

@ -70,6 +70,9 @@ html[data-route$='create'] main,
html[data-route$='edit'] main {
@extend %content-container-restricted;
}
html:not([data-route$='index']):not([data-route$='instances']) main {
margin-bottom: 2em;
}
@media #{$--lt-spacious-page-header} {
.actions button.copy-btn {

View File

@ -1,5 +1,7 @@
<form>
<fieldset>
<fieldset
disabled={{if (not (can "write acl" item=item)) "disabled"}}
>
<label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span>
<Input @value={{item.Name}} @name="name" @autofocus="autofocus" />
@ -25,12 +27,16 @@
{{/if}}
</fieldset>
<div>
{{#if create }}
{{#if (and create (can "create acls")) }}
{{! we only need to check for an empty name here as ember munges autofocus, once we have autofocus back revisit this}}
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>{{else}}<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
{{else}}
{{#if (can "write acl" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button>
{{# if (and (not create) (not-eq item.ID 'anonymous')) }}
{{# if (and (not create) (can "delete acl" item=item) ) }}
<ConfirmationDialog @message="Are you sure you want to delete this ACL token?">
<BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>

View File

@ -25,6 +25,7 @@
<CopyButton @value={{item.ID}} @name="token ID">
Copy token ID
</CopyButton>
{{#if (can "duplicate acl" item=item)}}
<button type="button" {{ action "clone" item }}>Clone token</button>
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|>
@ -38,6 +39,7 @@
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot>
</ConfirmationDialog>
{{/if}}
{{/if}}
</BlockSlot>
<BlockSlot @name="content">

View File

@ -32,7 +32,9 @@ as |sort filters items|}}
<label for="toolbar-toggle"></label>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create acls")}}
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0) }}

View File

@ -7,14 +7,16 @@
<TokenList @caption="Applied to the following tokens:" @items={{items}} />
{{/if}}
<div>
{{#if create }}
{{#if (and create (can "create tokens")) }}
{{! we only need to check for an empty name here as ember munges autofocus, once we have autofocus back revisit this}}
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
{{ else }}
{{#if (can "write policy" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button>
{{# if (not create) }}
{{# if (and (not create) (can "delete policy" item=item) ) }}
<ConfirmationDialog @message="Are you sure you want to delete this Policy?">
<BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button>

View File

@ -30,10 +30,10 @@
{{#if create }}
New Policy
{{else}}
{{#if (eq (policy/typeof item) 'policy-management')}}
View Policy
{{else}}
{{#if (can "write policy" item=item)}}
Edit Policy
{{else}}
View Policy
{{/if}}
{{/if}}
{{else}}

View File

@ -8,14 +8,16 @@
<TokenList @caption="Tokens" @items={{items}} />
{{/if}}
<div>
{{#if create }}
{{#if (and create (can "create roles")) }}
{{! we only need to check for an empty name here as ember munges autofocus, once we have autofocus back revisit this}}
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
{{ else }}
{{#if (can "write role" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button>
{{# if (not create) }}
{{# if (and (not create) (can "delete role" item=item) ) }}
<ConfirmationDialog @message="Are you sure you want to delete this Role?">
<BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button>

View File

@ -1,4 +1,6 @@
<fieldset>
<fieldset
disabled={{if (not (can "write token" item=item)) "disabled"}}
>
<label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span>
<Input @value={{item.Description}} @name="name" @autofocus="autofocus" />

View File

@ -1,4 +1,6 @@
<fieldset>
<fieldset
disabled={{if (not (can "write token" item=item)) "disabled"}}
>
{{#if create }}
<div class="type-toggle">
<label>
@ -15,9 +17,19 @@
</fieldset>
<fieldset id="roles">
<h2>Roles</h2>
<RoleSelector @dc={{dc}} @nspace={{nspace}} @items={{item.Roles}} />
<RoleSelector
@disabled={{not (can "write token" item=item)}}
@dc={{dc}}
@nspace={{nspace}}
@items={{item.Roles}}
/>
</fieldset>
<fieldset id="policies">
<h2>Policies</h2>
<PolicySelector @dc={{dc}} @nspace={{nspace}} @items={{item.Policies}} />
<PolicySelector
@disabled={{not (can "write token" item=item)}}
@dc={{dc}}
@nspace={{nspace}}
@items={{item.Policies}}
/>
</fieldset>

View File

@ -6,14 +6,17 @@
{{/if}}
{{!TODO: Make this into a slotted component}}
<div>
{{#if create }}
{{#if (and create (can "create tokens")) }}
{{! new tokens can be saved without you filling anything in, old tokens remain using isPristine }}
<button type="submit" {{ action "create" item}} disabled={{if (or (and (token/is-legacy item) item.isPristine) item.isInvalid) 'disabled'}}>Save</button>
{{ else }}
{{#if (can "write token" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button>
{{# if (and (not create) (not (token/is-anonymous item)) (not-eq item.AccessorID token.AccessorID) ) }}
{{# if (and (not create) (can "delete token" item=item token=token) ) }}
<ConfirmationDialog @message="Are you sure you want to delete this Token?">
<BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button>

View File

@ -53,7 +53,7 @@
</BlockSlot>
</ConfirmationDialog>
{{/if}}
{{#if (not (token/is-legacy item))}}
{{#if (can "duplicate token" item=item)}}
<button data-test-clone type="button" {{ action "clone" item }}>Duplicate</button>
{{/if}}
{{/if}}

View File

@ -48,7 +48,9 @@ as |sort filters items|}}
</h1>
</BlockSlot>
<BlockSlot @name="actions">
{{#if (can "create tokens")}}
<a data-test-create href="{{href-to 'dc.acls.tokens.create'}}" class="type-create">Create</a>
{{/if}}
</BlockSlot>
<BlockSlot @name="toolbar">
{{#if (gt items.length 0)}}

View File

@ -1,5 +1,7 @@
<form>
<fieldset>
<fieldset
disabled={{if (not (can "write nspace" item=item)) "disabled"}}
>
{{#if create }}
<label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span>
@ -21,26 +23,47 @@
<fieldset id="roles">
<h2>Roles</h2>
<p>
{{#if (can "write nspace" item=item)}}
By adding roles to this namespaces, you will apply them to all tokens created within this namespace.
{{else}}
The following roles are applied to all tokens created within this namespace.
{{/if}}
</p>
<RoleSelector @dc={{dc}} @nspace="default" @items={{item.ACLs.RoleDefaults}} />
<RoleSelector
@disabled={{not (can "write nspace" item=item)}}
@dc={{dc}}
@nspace="default"
@items={{item.ACLs.RoleDefaults}}
/>
</fieldset>
<fieldset id="policies">
<h2>Policies</h2>
<p>
{{#if (can "write nspace" item=item)}}
By adding policies to this namespaces, you will apply them to all tokens created within this namespace.
{{else}}
The following policies are applied to all tokens created within this namespace.
{{/if}}
</p>
<PolicySelector @dc={{dc}} @nspace="default" @allowIdentity={{false}} @items={{item.ACLs.PolicyDefaults}} />
<PolicySelector
@disabled={{not (can "write nspace" item=item)}}
@dc={{dc}}
@nspace="default"
@allowIdentity={{false}}
@items={{item.ACLs.PolicyDefaults}}
/>
</fieldset>
{{/if}}
<div>
{{#if create }}
{{#if (and create (can "create nspaces")) }}
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid) 'disabled'}}>Save</button>
{{ else }}
{{else}}
{{#if (can "write nspace" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button>
{{# if (and (not create) (not-eq item.Name 'default')) }}
{{# if (and (not create) (can "delete nspace" item=item) ) }}
<ConfirmationDialog @message="Are you sure you want to delete this Namespace?">
<BlockSlot @name="action" as |confirm|>
<button data-test-delete type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>