diff --git a/addons/api/addon/abilities/role.js b/addons/api/addon/abilities/role.js
new file mode 100644
index 000000000..f0c6d62ec
--- /dev/null
+++ b/addons/api/addon/abilities/role.js
@@ -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');
+ }
+}
diff --git a/addons/api/app/abilities/role.js b/addons/api/app/abilities/role.js
new file mode 100644
index 000000000..aefb6fd13
--- /dev/null
+++ b/addons/api/app/abilities/role.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/role';
diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js
index a057fc29f..be13712b8 100644
--- a/addons/api/mirage/factories/role.js
+++ b/addons/api/mirage/factories/role.js
@@ -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: () => [
diff --git a/addons/api/tests/unit/abilities/role-test.js b/addons/api/tests/unit/abilities/role-test.js
new file mode 100644
index 000000000..ffaf6a11d
--- /dev/null
+++ b/addons/api/tests/unit/abilities/role-test.js
@@ -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));
+ });
+});
diff --git a/ui/admin/app/components/form/role/grants/index.hbs b/ui/admin/app/components/form/role/grants/index.hbs
index 79766a2e1..d6aaad424 100644
--- a/ui/admin/app/components/form/role/grants/index.hbs
+++ b/ui/admin/app/components/form/role/grants/index.hbs
@@ -1,39 +1,43 @@
-
-
- {{! Add grant field (only one per form) }}
-
-
-
- {{t 'resources.role.grant.actions.create'}}
-
-
-
-
-
-
- {{t 'actions.add'}}
-
-
-
-
-
-
+{{#if (can 'setGrants role' @model)}}
+
+
+ {{! Add grant field (only one per form) }}
+
+
+
+ {{t
+ 'resources.role.grant.actions.create'
+ }}
+
+
+
+
+
+
+ {{t 'actions.add'}}
+
+
+
+
+
+
+{{/if}}
@@ -72,23 +77,27 @@
-
- {{t 'actions.remove'}}
-
+ {{#if (can 'setGrants role' @model)}}
+
+ {{t 'actions.remove'}}
+
+ {{/if}}
{{/each}}
-
+ {{#if (can 'setGrants role' @model)}}
+
+ {{/if}}
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
index 88add065c..6c01fcc16 100644
--- a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
+++ b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs
@@ -3,9 +3,11 @@
@dropdownRight={{true}}
as |dropdown|
>
-
- {{t 'resources.role.principal.actions.add-principals'}}
-
+ {{#if (can 'addPrincipals role' @model)}}
+
+ {{t 'resources.role.principal.actions.add-principals'}}
+
+ {{/if}}
{{#if (can 'delete model' @model)}}
-
-
- {{t 'actions.remove'}}
-
-
+
+ {{t 'actions.remove'}}
+
+
+ {{/if}}
{{/each}}
diff --git a/ui/admin/tests/acceptance/roles/grants-test.js b/ui/admin/tests/acceptance/roles/grants-test.js
index d13eb4f46..fd169aa46 100644
--- a/ui/admin/tests/acceptance/roles/grants-test.js
+++ b/ui/admin/tests/acceptance/roles/grants-test.js
@@ -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(
diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js
index b26cfe398..998c9acc5 100644
--- a/ui/admin/tests/acceptance/roles/principals-test.js
+++ b/ui/admin/tests/acceptance/roles/principals-test.js
@@ -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: [] });