diff --git a/ui/app/abilities/client.js b/ui/app/abilities/client.js new file mode 100644 index 000000000..d6a9c0cb3 --- /dev/null +++ b/ui/app/abilities/client.js @@ -0,0 +1,25 @@ +import { Ability } from 'ember-can'; +import { inject as service } from '@ember/service'; +import { computed, get } from '@ember/object'; +import { equal, or } from '@ember/object/computed'; + +export default Ability.extend({ + token: service(), + + // Map abilities to policy options (which are coarse for nodes) + // instead of specific behaviors. + canWrite: or('selfTokenIsManagement', 'policiesIncludeNodeWrite'), + + selfTokenIsManagement: equal('token.selfToken.type', 'management'), + + policiesIncludeNodeWrite: computed('token.selfTokenPolicies.[]', function() { + // For each policy record, extract the Node policy + const policies = (this.get('token.selfTokenPolicies') || []) + .toArray() + .map(policy => get(policy, 'rulesJSON.Node.Policy')) + .compact(); + + // Node write is allowed if any policy allows it + return policies.some(policy => policy === 'write'); + }), +}); diff --git a/ui/tests/unit/abilities/client-test.js b/ui/tests/unit/abilities/client-test.js new file mode 100644 index 000000000..1e488709c --- /dev/null +++ b/ui/tests/unit/abilities/client-test.js @@ -0,0 +1,88 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import Service from '@ember/service'; + +function setupAbility(ability, hooks) { + hooks.beforeEach(function() { + this.ability = this.owner.lookup(`ability:${ability}`); + }); + + hooks.afterEach(function() { + delete this.ability; + }); +} + +module('Unit | Ability | client', function(hooks) { + setupTest(hooks); + setupAbility('client', hooks); + + test('it permits client write for management tokens', function(assert) { + const mockToken = Service.extend({ + selfToken: { type: 'management' }, + }); + this.owner.register('service:token', mockToken); + + assert.ok(this.ability.canWrite); + }); + + test('it permits client write for tokens with a policy that has node-write', function(assert) { + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJSON: { + Node: { + Policy: 'write', + }, + }, + }, + ], + }); + this.owner.register('service:token', mockToken); + + assert.ok(this.ability.canWrite); + }); + + test('it permits client write for tokens with a policy that allows write and another policy that disallows it', function(assert) { + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJSON: { + Node: { + Policy: 'write', + }, + }, + }, + { + rulesJSON: { + Node: { + Policy: 'read', + }, + }, + }, + ], + }); + this.owner.register('service:token', mockToken); + + assert.ok(this.ability.canWrite); + }); + + test('it blocks client write for tokens with a policy that does not allow node-write', function(assert) { + const mockToken = Service.extend({ + selfToken: { type: 'client' }, + selfTokenPolicies: [ + { + rulesJSON: { + Node: { + Policy: 'read', + }, + }, + }, + ], + }); + this.owner.register('service:token', mockToken); + + assert.notOk(this.ability.canWrite); + }); +});