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 6fe45c075f
commit fa6687b7f4
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 BaseAbility from './base';
import { inject as service } from '@ember/service'; 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 { export default class ACLAbility extends BaseAbility {
@service('env') env; @service('env') env;
@ -13,4 +10,10 @@ export default class ACLAbility extends BaseAbility {
get canRead() { get canRead() {
return this.env.var('CONSUL_ACLS_ENABLED') && super.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; return this.canCreate;
} }
get canDelete() {
return this.item.Name !== 'default' && super.canDelete;
}
get canChoose() { get canChoose() {
return this.env.var('CONSUL_NSPACES_ENABLED') && this.nspaces.length > 0; 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 ...attributes
> >
{{yield}} {{yield}}
{{#if (not disabled)}}
<YieldSlot @name="create">{{yield}}</YieldSlot> <YieldSlot @name="create">{{yield}}</YieldSlot>
<label class="type-text"> <label class="type-text">
<span><YieldSlot @name="label">{{yield}}</YieldSlot></span> <span><YieldSlot @name="label">{{yield}}</YieldSlot></span>
@ -35,6 +36,7 @@
</PowerSelect> </PowerSelect>
</DataCollection> </DataCollection>
</label> </label>
{{/if}}
{{#if (gt items.length 0)}} {{#if (gt items.length 0)}}
<YieldSlot @name="set">{{yield}}</YieldSlot> <YieldSlot @name="set">{{yield}}</YieldSlot>
{{else}} {{else}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
{{yield}} {{yield}}
<fieldset ...attributes> <fieldset
disabled={{if (not (can "write policy" item=item)) "disabled"}}
...attributes
>
{{#yield-slot name='template'}} {{#yield-slot name='template'}}
{{else}} {{else}}
<header> <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}} {{yield}}
<BlockSlot @name="label"> <BlockSlot @name="label">
Apply an existing policy Apply an existing policy
@ -112,6 +121,7 @@
/> />
{{/if}} {{/if}}
</label> </label>
{{#if (not disabled)}}
<div> <div>
<ConfirmationDialog @message="Are you sure you want to remove this policy from this token?"> <ConfirmationDialog @message="Are you sure you want to remove this policy from this token?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
@ -127,6 +137,7 @@
</BlockSlot> </BlockSlot>
</ConfirmationDialog> </ConfirmationDialog>
</div> </div>
{{/if}}
</BlockSlot> </BlockSlot>
</TabularDetails> </TabularDetails>

View File

@ -1,5 +1,9 @@
{{yield}} {{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'}}"> <label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span> <span>Name</span>
<input type="text" value={{item.Name}} name="role[Name]" autofocus="autofocus" oninput={{action 'change'}} /> <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-slot name='policy' params=(block-params item)}}
{{yield}} {{yield}}
{{else}} {{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}} {{/yield-slot}}
</fieldset> </fieldset>

View File

@ -59,7 +59,7 @@ as |modal|>
</BlockSlot> </BlockSlot>
</ModalDialog> </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"> <BlockSlot @name="label">
Apply an existing role Apply an existing role
</BlockSlot> </BlockSlot>
@ -70,7 +70,6 @@ as |modal|>
> >
<span>Create new role</span> <span>Create new role</span>
</label> </label>
</BlockSlot> </BlockSlot>
<BlockSlot @name="option" as |option|> <BlockSlot @name="option" as |option|>
{{option.Name}} {{option.Name}}
@ -100,8 +99,15 @@ as |modal|>
</BlockSlot> </BlockSlot>
<BlockSlot @name="menu" as |confirm send keypressClick|> <BlockSlot @name="menu" as |confirm send keypressClick|>
<li role="none"> <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> </li>
{{#if (not disabled)}}
<li role="none" class="dangerous"> <li role="none" class="dangerous">
<label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Remove</label> <label for={{confirm}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Remove</label>
<div role="menu"> <div role="menu">
@ -125,6 +131,7 @@ as |modal|>
</div> </div>
</div> </div>
</li> </li>
{{/if}}
</BlockSlot> </BlockSlot>
</PopoverMenu> </PopoverMenu>
</BlockSlot> </BlockSlot>

View File

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

View File

@ -4,12 +4,10 @@ import CreatingRoute from 'consul-ui/mixins/creating-route';
export default class CreateRoute extends Route.extend(CreatingRoute) { export default class CreateRoute extends Route.extend(CreatingRoute) {
templateName = 'dc/nspaces/edit'; templateName = 'dc/nspaces/edit';
beforeModel() { async beforeModel() {
// TODO: Update nspace CRUD to use Data components
// we need to skip CreatingRoute.beforeModel here // we need to skip CreatingRoute.beforeModel here
// TODO(octane): ideally we'd like to call Route.beforeModel // but still call Route.beforeModel
// but its not clear how to do that with old ember return Route.prototype.beforeModel.apply(this, arguments);
// 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
} }
} }

View File

@ -16,14 +16,19 @@ export default class BaseRoute extends Route {
* Inspects a custom `abilities` array on the router for this route. Every * 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 * 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 * 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. * before any calls to the backend for this model/route.
*/ */
async beforeModel() { 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.length > 0) {
if (!abilities.every(ability => this.permissions.can(ability))) { 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', Resource: 'operator',
Access: 'write', Access: 'write',
}, },
{
Resource: 'operator',
Access: 'read',
},
{ {
Resource: 'service', Resource: 'service',
Access: 'read', Access: 'read',

View File

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

View File

@ -1,5 +1,7 @@
<form> <form>
<fieldset> <fieldset
disabled={{if (not (can "write acl" item=item)) "disabled"}}
>
<label class="type-text{{if item.error.Name ' has-error'}}"> <label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span> <span>Name</span>
<Input @value={{item.Name}} @name="name" @autofocus="autofocus" /> <Input @value={{item.Name}} @name="name" @autofocus="autofocus" />
@ -25,12 +27,16 @@
{{/if}} {{/if}}
</fieldset> </fieldset>
<div> <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}} {{! 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}} {{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button> <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?"> <ConfirmationDialog @message="Are you sure you want to delete this ACL token?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item parent}}>Delete</button> <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"> <CopyButton @value={{item.ID}} @name="token ID">
Copy token ID Copy token ID
</CopyButton> </CopyButton>
{{#if (can "duplicate acl" item=item)}}
<button type="button" {{ action "clone" item }}>Clone token</button> <button type="button" {{ action "clone" item }}>Clone token</button>
<ConfirmationDialog @message="Are you sure you want to use this ACL token?"> <ConfirmationDialog @message="Are you sure you want to use this ACL token?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
@ -38,6 +39,7 @@
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button> <button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
</BlockSlot> </BlockSlot>
</ConfirmationDialog> </ConfirmationDialog>
{{/if}}
{{/if}} {{/if}}
</BlockSlot> </BlockSlot>
<BlockSlot @name="content"> <BlockSlot @name="content">

View File

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

View File

@ -7,14 +7,16 @@
<TokenList @caption="Applied to the following tokens:" @items={{items}} /> <TokenList @caption="Applied to the following tokens:" @items={{items}} />
{{/if}} {{/if}}
<div> <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}} {{! 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> <button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
{{ else }} {{ else }}
{{#if (can "write policy" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button> <button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}} {{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button> <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?"> <ConfirmationDialog @message="Are you sure you want to delete this Policy?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button> <button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button>

View File

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

View File

@ -8,14 +8,16 @@
<TokenList @caption="Tokens" @items={{items}} /> <TokenList @caption="Tokens" @items={{items}} />
{{/if}} {{/if}}
<div> <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}} {{! 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> <button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
{{ else }} {{ else }}
{{#if (can "write role" item=item)}}
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button> <button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}} {{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button> <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?"> <ConfirmationDialog @message="Are you sure you want to delete this Role?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item}}>Delete</button> <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'}}"> <label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span> <span>Name</span>
<Input @value={{item.Description}} @name="name" @autofocus="autofocus" /> <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 }} {{#if create }}
<div class="type-toggle"> <div class="type-toggle">
<label> <label>
@ -15,9 +17,19 @@
</fieldset> </fieldset>
<fieldset id="roles"> <fieldset id="roles">
<h2>Roles</h2> <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>
<fieldset id="policies"> <fieldset id="policies">
<h2>Policies</h2> <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> </fieldset>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
<form> <form>
<fieldset> <fieldset
disabled={{if (not (can "write nspace" item=item)) "disabled"}}
>
{{#if create }} {{#if create }}
<label class="type-text{{if item.error.Name ' has-error'}}"> <label class="type-text{{if item.error.Name ' has-error'}}">
<span>Name</span> <span>Name</span>
@ -21,26 +23,47 @@
<fieldset id="roles"> <fieldset id="roles">
<h2>Roles</h2> <h2>Roles</h2>
<p> <p>
{{#if (can "write nspace" item=item)}}
By adding roles to this namespaces, you will apply them to all tokens created within this namespace. 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> </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>
<fieldset id="policies"> <fieldset id="policies">
<h2>Policies</h2> <h2>Policies</h2>
<p> <p>
{{#if (can "write nspace" item=item)}}
By adding policies to this namespaces, you will apply them to all tokens created within this namespace. 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> </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> </fieldset>
{{/if}} {{/if}}
<div> <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> <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> <button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
{{/if}}
{{/if}} {{/if}}
<button type="reset" {{ action "cancel" item}}>Cancel</button> <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?"> <ConfirmationDialog @message="Are you sure you want to delete this Namespace?">
<BlockSlot @name="action" as |confirm|> <BlockSlot @name="action" as |confirm|>
<button data-test-delete type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button> <button data-test-delete type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>