diff --git a/addons/api/addon/abilities/collection.js b/addons/api/addon/abilities/collection.js new file mode 100644 index 000000000..ad52afd18 --- /dev/null +++ b/addons/api/addon/abilities/collection.js @@ -0,0 +1,36 @@ +import { Ability } from 'ember-can'; + +/** + * Generic ability provides abilities common to all models in the API. + */ +export default class CollectionAbility extends Ability { + // =permissions + + /** + * @type {boolean} + */ + get canList() { + return this.hasAuthorizedCollectionAction('list'); + } + + /** + * @type {boolean} + */ + get canCreate() { + return this.hasAuthorizedCollectionAction('create'); + } + + // =methods + + /** + * Returns true if the given action is contained in the ability model's + * `authorized_collection_actions` array. + * @param {string} action + * @return {boolean} + */ + hasAuthorizedCollectionAction(action) { + const { authorized_collection_actions } = this.model; + const collection = this.collection || {}; + return authorized_collection_actions[collection]?.includes(action); + } +} diff --git a/addons/api/app/abilities/collection.js b/addons/api/app/abilities/collection.js new file mode 100644 index 000000000..041825d2d --- /dev/null +++ b/addons/api/app/abilities/collection.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/collection'; diff --git a/addons/api/tests/unit/abilities/collection-test.js b/addons/api/tests/unit/abilities/collection-test.js new file mode 100644 index 000000000..b810e6939 --- /dev/null +++ b/addons/api/tests/unit/abilities/collection-test.js @@ -0,0 +1,38 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Collection', function (hooks) { + setupTest(hooks); + + test('it reflects when a resource may be listed based on authorized_collection_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_collection_actions: { + foobars: ['list'], + }, + }; + assert.ok(service.can('list collection', model, { collection: 'foobars' })); + model.authorized_collection_actions.foobars = []; + assert.notOk( + service.can('list collection', model, { collection: 'foobars' }) + ); + }); + + test('it reflects when a resource may be created based on authorized_collection_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_collection_actions: { + foobars: ['create'], + }, + }; + assert.ok( + service.can('create collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = []; + assert.notOk( + service.can('create collection', model, { collection: 'foobars' }) + ); + }); +}); diff --git a/ui/admin/app/abilities/collection.js b/ui/admin/app/abilities/collection.js new file mode 100644 index 000000000..1da414d43 --- /dev/null +++ b/ui/admin/app/abilities/collection.js @@ -0,0 +1,30 @@ +import CollectionAbility from 'api/abilities/collection'; +import { inject as service } from '@ember/service'; + +export default class OverrideCollectionAbility extends CollectionAbility { + // =services + + @service features; + + // =permissions + + /** + * Navigating to a resource is allowed if either list or create grants + * are present. + * @type {boolean} + */ + get canNavigate() { + return this.canList || this.canCreate; + } + + // =methods + + /** + * If the capabilities feature flag is disabled, actions are always + * authorized. This mimics pre-capabilities behavior. + */ + hasAuthorizedCollectionAction() { + if (!this.features.isEnabled('capabilities')) return true; + return super.hasAuthorizedCollectionAction(...arguments); + } +} diff --git a/ui/admin/tests/unit/abilities/collection-test.js b/ui/admin/tests/unit/abilities/collection-test.js new file mode 100644 index 000000000..586eb52b4 --- /dev/null +++ b/ui/admin/tests/unit/abilities/collection-test.js @@ -0,0 +1,29 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Collection', function (hooks) { + setupTest(hooks); + + test('it reflects when a resource may be navigated to based on list and create actions', function (assert) { + assert.expect(4); + const service = this.owner.lookup('service:can'); + const model = { + authorized_collection_actions: { foobars: [] }, + }; + assert.notOk( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['list']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['create']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['list', 'create']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + }); +}); diff --git a/ui/desktop/app/abilities/collection.js b/ui/desktop/app/abilities/collection.js new file mode 100644 index 000000000..1da414d43 --- /dev/null +++ b/ui/desktop/app/abilities/collection.js @@ -0,0 +1,30 @@ +import CollectionAbility from 'api/abilities/collection'; +import { inject as service } from '@ember/service'; + +export default class OverrideCollectionAbility extends CollectionAbility { + // =services + + @service features; + + // =permissions + + /** + * Navigating to a resource is allowed if either list or create grants + * are present. + * @type {boolean} + */ + get canNavigate() { + return this.canList || this.canCreate; + } + + // =methods + + /** + * If the capabilities feature flag is disabled, actions are always + * authorized. This mimics pre-capabilities behavior. + */ + hasAuthorizedCollectionAction() { + if (!this.features.isEnabled('capabilities')) return true; + return super.hasAuthorizedCollectionAction(...arguments); + } +} diff --git a/ui/desktop/tests/unit/abilities/collection-test.js b/ui/desktop/tests/unit/abilities/collection-test.js new file mode 100644 index 000000000..586eb52b4 --- /dev/null +++ b/ui/desktop/tests/unit/abilities/collection-test.js @@ -0,0 +1,29 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Collection', function (hooks) { + setupTest(hooks); + + test('it reflects when a resource may be navigated to based on list and create actions', function (assert) { + assert.expect(4); + const service = this.owner.lookup('service:can'); + const model = { + authorized_collection_actions: { foobars: [] }, + }; + assert.notOk( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['list']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['create']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + model.authorized_collection_actions.foobars = ['list', 'create']; + assert.ok( + service.can('navigate collection', model, { collection: 'foobars' }) + ); + }); +});