Merge pull request #783 from hashicorp/feature-role-capabilities

Feature role capabilities
This commit is contained in:
Randall Morey authored and GitHub committed 2021-10-04 16:00:30 -04:00
commit e8090b9d91
9 files changed
+194 -66

No files matched your search

+29
View File
@@ -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');
}
}
+1
View File
@@ -0,0 +1 @@
export { default } from 'api/abilities/role';
+3
View File
@@ -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: [] });