Merge pull request #783 from hashicorp/feature-role-capabilities
Feature role capabilities
This commit is contained in:
9 files changed
+194
-66
No files matched your search
@@ -0,0 +1,29 @@
|
||||
import ModelAbility from './model';
|
||||
|
||||
/**
|
||||
* Provides abilities for roles.
|
||||
*/
|
||||
export default class RoleAbility extends ModelAbility {
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canSetGrants() {
|
||||
return this.hasAuthorizedAction('set-grants');
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canAddPrincipals() {
|
||||
return this.hasAuthorizedAction('add-principals');
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canRemovePrincipals() {
|
||||
return this.hasAuthorizedAction('remove-principals');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/abilities/role';
|
||||
@@ -9,6 +9,9 @@ export default factory.extend({
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'set-grants',
|
||||
'add-principals',
|
||||
'remove-principals',
|
||||
],
|
||||
// eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects
|
||||
grant_strings: () => [
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Abilities | Account', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it reflects when a given account may set grants based on authorized_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_actions: ['set-grants'],
|
||||
};
|
||||
assert.ok(service.can('setGrants role', model));
|
||||
model.authorized_actions = [];
|
||||
assert.notOk(service.can('setGrants role', model));
|
||||
});
|
||||
|
||||
test('it reflects when a given account may add principals based on authorized_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_actions: ['add-principals'],
|
||||
};
|
||||
assert.ok(service.can('addPrincipals role', model));
|
||||
model.authorized_actions = [];
|
||||
assert.notOk(service.can('addPrincipals role', model));
|
||||
});
|
||||
|
||||
test('it reflects when a given account may remove principals based on authorized_actions', function (assert) {
|
||||
assert.expect(2);
|
||||
const service = this.owner.lookup('service:can');
|
||||
const model = {
|
||||
authorized_actions: ['remove-principals'],
|
||||
};
|
||||
assert.ok(service.can('removePrincipals role', model));
|
||||
model.authorized_actions = [];
|
||||
assert.notOk(service.can('removePrincipals role', model));
|
||||
});
|
||||
});
|
||||
@@ -1,39 +1,43 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{fn this.createGrant @addGrant}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
as |form|
|
||||
>
|
||||
<Rose::List::KeyValue as |list|>
|
||||
{{! Add grant field (only one per form) }}
|
||||
<list.item as |item|>
|
||||
<form.input
|
||||
@contextual={{true}}
|
||||
@name='grant'
|
||||
@type='text'
|
||||
@value={{this.newGrantString}}
|
||||
as |input|
|
||||
>
|
||||
<item.key>
|
||||
<input.label>{{t 'resources.role.grant.actions.create'}}</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field title={{t 'form.grant.help'}} />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button
|
||||
@submit={{true}}
|
||||
@disabled={{this.cannotSave}}
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</item.cell>
|
||||
</form.input>
|
||||
</list.item>
|
||||
</Rose::List::KeyValue>
|
||||
</Rose::Form>
|
||||
{{#if (can 'setGrants role' @model)}}
|
||||
<Rose::Form
|
||||
@onSubmit={{fn this.createGrant @addGrant}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
as |form|
|
||||
>
|
||||
<Rose::List::KeyValue as |list|>
|
||||
{{! Add grant field (only one per form) }}
|
||||
<list.item as |item|>
|
||||
<form.input
|
||||
@contextual={{true}}
|
||||
@name='grant'
|
||||
@type='text'
|
||||
@value={{this.newGrantString}}
|
||||
as |input|
|
||||
>
|
||||
<item.key>
|
||||
<input.label>{{t
|
||||
'resources.role.grant.actions.create'
|
||||
}}</input.label>
|
||||
</item.key>
|
||||
<item.cell>
|
||||
<input.field title={{t 'form.grant.help'}} />
|
||||
</item.cell>
|
||||
<item.cell>
|
||||
<Rose::Button
|
||||
@submit={{true}}
|
||||
@disabled={{this.cannotSave}}
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</item.cell>
|
||||
</form.input>
|
||||
</list.item>
|
||||
</Rose::List::KeyValue>
|
||||
</Rose::Form>
|
||||
{{/if}}
|
||||
|
||||
<Rose::Form
|
||||
@onSubmit={{fn @submit this.grantStrings}}
|
||||
@@ -61,6 +65,7 @@
|
||||
@name='grant'
|
||||
@type='text'
|
||||
@value={{grant.value}}
|
||||
@disabled={{if (can 'setGrants role' @model) false true}}
|
||||
@error={{@model.errors.grant_strings}}
|
||||
as |input|
|
||||
>
|
||||
@@ -72,23 +77,27 @@
|
||||
</item.cell>
|
||||
</form.input>
|
||||
<item.cell>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
@iconOnly='flight-icons/svg/trash-16'
|
||||
title={{t 'actions.remove'}}
|
||||
{{on 'click' (fn @removeGrant grant.value)}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
{{#if (can 'setGrants role' @model)}}
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
@iconOnly='flight-icons/svg/trash-16'
|
||||
title={{t 'actions.remove'}}
|
||||
{{on 'click' (fn @removeGrant grant.value)}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
{{/if}}
|
||||
</item.cell>
|
||||
</list.item>
|
||||
{{/each}}
|
||||
</Rose::List::KeyValue>
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{#if (can 'setGrants role' @model)}}
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
</Rose::Form>
|
||||
@@ -3,9 +3,11 @@
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.link @route='scopes.scope.roles.role.add-principals'>
|
||||
{{t 'resources.role.principal.actions.add-principals'}}
|
||||
</dropdown.link>
|
||||
{{#if (can 'addPrincipals role' @model)}}
|
||||
<dropdown.link @route='scopes.scope.roles.role.add-principals'>
|
||||
{{t 'resources.role.principal.actions.add-principals'}}
|
||||
</dropdown.link>
|
||||
{{/if}}
|
||||
{{#if (can 'delete model' @model)}}
|
||||
<dropdown.separator />
|
||||
<dropdown.button
|
||||
|
||||
@@ -30,21 +30,26 @@
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Dropdown
|
||||
@icon='flight-icons/svg/more-horizontal-16'
|
||||
@iconOnly={{true}}
|
||||
@showCaret={{false}}
|
||||
@dropdownRight={{true}}
|
||||
@text='{{t 'actions.manage'}}'
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
{{on 'click' (route-action 'removePrincipal' principal.model)}}
|
||||
{{#if (can 'removePrincipals role' @model.role)}}
|
||||
<Rose::Dropdown
|
||||
@icon='flight-icons/svg/more-horizontal-16'
|
||||
@iconOnly={{true}}
|
||||
@showCaret={{false}}
|
||||
@dropdownRight={{true}}
|
||||
@text='{{t 'actions.manage'}}'
|
||||
as |dropdown|
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</dropdown.button>
|
||||
</Rose::Dropdown>
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
{{on
|
||||
'click'
|
||||
(route-action 'removePrincipal' principal.model)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</dropdown.button>
|
||||
</Rose::Dropdown>
|
||||
{{/if}}
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
@@ -63,6 +63,26 @@ module('Acceptance | roles | grants', function (hooks) {
|
||||
assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount);
|
||||
});
|
||||
|
||||
test('cannot set grants without proper authorization', async function (assert) {
|
||||
assert.expect(4);
|
||||
const authorized_actions = instances.role.authorized_actions.filter(
|
||||
(item) => item !== 'set-grants'
|
||||
);
|
||||
instances.role.update({ authorized_actions });
|
||||
await visit(urls.grants);
|
||||
assert.equal(
|
||||
findAll('main form').length,
|
||||
1,
|
||||
'New grant form is not displayed.'
|
||||
);
|
||||
assert.notOk(
|
||||
find('main form button:not([type="submit"])'),
|
||||
'Grant delete button is not displayed.'
|
||||
);
|
||||
assert.notOk(find('.rose-form-actions'), 'Form actions are not displayed.');
|
||||
assert.ok(find('main form input[disabled]'), 'Grant fields are disabled.');
|
||||
});
|
||||
|
||||
test('update a grant', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post(
|
||||
|
||||
@@ -69,6 +69,16 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
assert.equal(findAll('tbody tr').length, principalsCount - 1);
|
||||
});
|
||||
|
||||
test('principal cannot be removed from a role without proper authorization', async function (assert) {
|
||||
assert.expect(1);
|
||||
const authorized_actions = instances.role.authorized_actions.filter(
|
||||
(item) => item !== 'remove-principals'
|
||||
);
|
||||
instances.role.update({ authorized_actions });
|
||||
await visit(urls.rolePrincipals);
|
||||
assert.notOk(find('tbody tr .rose-dropdown-button-danger'));
|
||||
});
|
||||
|
||||
test('shows error message on principal remove', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.server.post('/roles/:idMethod', () => {
|
||||
@@ -89,6 +99,16 @@ module('Acceptance | roles | principals', function (hooks) {
|
||||
assert.ok(find('[role="alert"]'));
|
||||
});
|
||||
|
||||
test('cannot navigate to add principals without proper authorization', async function (assert) {
|
||||
assert.expect(1);
|
||||
const authorized_actions = instances.role.authorized_actions.filter(
|
||||
(item) => item !== 'add-principals'
|
||||
);
|
||||
instances.role.update({ authorized_actions });
|
||||
await visit(urls.rolePrincipals);
|
||||
assert.notOk(find('.rose-layout-page-actions a'));
|
||||
});
|
||||
|
||||
test('select and save principals to add', async function (assert) {
|
||||
assert.expect(3);
|
||||
instances.role.update({ userIds: [], groupIds: [] });
|
||||
|
||||
Reference in new issue
Block a user