diff --git a/README.md b/README.md index bf9f3ad..d08a884 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ const acl = new Acl() acl.rule('view', Post) acl.rule('moderate', Post, (user) => user.isModerator()) acl.rule(['edit', 'delete'], Post, (user, post) => post.userId === user.id) +acl.rule('purgeInactive', user => user.isAdmin) ``` Policies are also supported: @@ -112,6 +113,35 @@ the subject name. See [subjectMapper](#subjectmapper) +### Additional Parameters and Global Rules + +You can define global rules by omitting the subject when defining rules. + +```javascript +acl.rule('purgeInactive', user => user.admin) +acl.can('purgeInactive') +``` + +Also you can pass additional parameters to the handler like this: + +```javascript +acl.rule('edit', Post, (user, post, verb, additionalParameter) => true) +acl.can('edit', post, additionalParameter) +``` + +However, you cannot combine the two without explicitly stating that you are +defining a global rule. You do this by importing the special `GlobalRule` +subject. + +```javascript +import {GlobalRule} from 'browser-acl' +acl.rule('purgeInactive', GlobalRule, user => user.admin) +acl.can('purgeInactive', GlobalRule, additionalParameter) +``` + +Note: When defining the rule you can omit it, but is is required for `can`. +This is only in the case when you need to pass additional parameters. + # API @@ -148,13 +178,14 @@ acl.rule('create', Post) acl.rule('edit', Post, (user, post) => post.userId === user.id) acl.rule('edit', Post, (user, post, verb, additionalParameter, secondAdditionalParameter) => true) acl.rule('delete', Post, false) // deleting disabled +acl.rule('purgeInactive', user => user.isAdmin) // global rule ``` **Parameters** -- `verbs` **([Array][14]<[string][15]> | [string][15])** -- `subject` **([Function][16] \| [Object][17] \| [string][15])** -- `test` **([Boolean][18] \| [Function][16])** =true (optional, default `true`) +- `verbs` **([Array][16]<[string][17]> | [string][17])** +- `subject` **([Function][18] \| [Object][14] \| [string][17])** ? +- `test` **([Boolean][15] \| [Function][18])** =true (optional, default `true`) Returns **[Acl][19]** @@ -183,8 +214,8 @@ Policies are useful for grouping rules and adding more complex logic. **Parameters** -- `policy` **[Object][17]** A policy with properties that are verbs -- `subject` **([Function][16] \| [Object][17] \| [string][15])** +- `policy` **[Object][14]** A policy with properties that are verbs +- `subject` **([Function][18] \| [Object][14] \| [string][17])** Returns **[Acl][19]** @@ -201,8 +232,8 @@ bud it can be used manually through `this.registry`. **Parameters** -- `klass` **[Function][16]** A class or constructor function -- `subjectName` **[string][15]** +- `klass` **[Function][18]** A class or constructor function +- `subjectName` **[string][17]** ### can @@ -232,9 +263,9 @@ the mixin: **Parameters** -- `user` **[Object][17]** -- `verb` **[string][15]** -- `subject` **([Function][16] \| [Object][17] \| [string][15])** +- `user` **[Object][14]** +- `verb` **[string][17]** +- `subject` **([Function][18] \| [Object][14] \| [string][17])** - `args` **...any** Any other param is passed into rule Returns **any** Boolean @@ -248,9 +279,9 @@ Note the subjects do not need to be of the same kind. **Parameters** -- `user` **[Object][17]** +- `user` **[Object][14]** - `verb` -- `subjects` **[Array][14]<([Function][16] \| [Object][17] \| [string][15])>** +- `subjects` **[Array][16]<([Function][18] \| [Object][14] \| [string][17])>** - `args` **...any** Any other param is passed into rule Returns **any** Boolean @@ -264,9 +295,9 @@ Note the subjects do not need to be of the same kind. **Parameters** -- `user` **[Object][17]** +- `user` **[Object][14]** - `verb` -- `subjects` **[Array][14]<([Function][16] \| [Object][17] \| [string][15])>** +- `subjects` **[Array][16]<([Function][18] \| [Object][14] \| [string][17])>** - `args` **...any** Any other param is passed into rule Returns **any** Boolean @@ -283,7 +314,7 @@ Acl instance. **Parameters** -- `User` **[Function][16]** A user class or contructor function +- `User` **[Function][18]** A user class or contructor function ### subjectMapper @@ -313,9 +344,9 @@ classes to subject name. **Parameters** -- `subject` **([Function][16] \| [Object][17] \| [string][15])** +- `subject` **([Function][18] \| [Object][14] \| [string][17])** -Returns **[string][15]** A subject +Returns **[string][17]** A subject ### reset @@ -331,8 +362,8 @@ Optionally limit to a single verb. **Parameters** -- `subject` **([Object][17] \| [Function][16] \| [String][15])** -- `verb` **[String][15]?** an optional verb (optional, default `null`) +- `subject` **([Object][14] \| [Function][18] \| [String][17])** +- `verb` **[String][17]?** an optional verb (optional, default `null`) Returns **[Acl][19]** @@ -342,7 +373,7 @@ Remove policy for subject **Parameters** -- `subject` **([Object][17] \| [Function][16] \| [String][15])** +- `subject` **([Object][14] \| [Function][18] \| [String][17])** Returns **[Acl][19]** @@ -352,7 +383,7 @@ Convenience method for removing all rules and policies for a subject **Parameters** -- `subject` **([Object][17] \| [Function][16] \| [String][15])** +- `subject` **([Object][14] \| [Function][18] \| [String][17])** Returns **[Acl][19]** @@ -382,14 +413,14 @@ Returns **[Acl][19]** [13]: #removeall -[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function [19]: #acl diff --git a/index.js b/index.js index 924f1e8..4819ac0 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,18 @@ + +export const GlobalRule = 'GLOBAL_RULE' + +const isNameless = fn => typeof fn === 'function' && fn.name === '' + /** * Simple ACL library for the browser inspired by Laravel's guards and policies. - * @class Acl */ -class Acl { +export default class Acl { /** * browser-acl * * @access public + * @param {Object} options * @param {Boolean} {strict=false}={} Errors out on unknown verbs when true * @returns {Acl} */ @@ -33,15 +38,20 @@ class Acl { * acl.rule('edit', Post, (user, post) => post.userId === user.id) * acl.rule('edit', Post, (user, post, verb, additionalParameter, secondAdditionalParameter) => true) * acl.rule('delete', Post, false) // deleting disabled + * acl.rule('purgeInactive', user => user.isAdmin) // global rule * ``` * * @access public * @param {Array|string} verbs - * @param {Function|Object|string} subject + * @param {Function|Object|string} subject? * @param {Boolean|Function} test=true * @returns {Acl} */ rule(verbs, subject, test = true) { + if (isNameless(subject)) { + test = subject + subject = GlobalRule + } const subjectName = this.subjectMapper(subject) const verbs_ = Array.isArray(verbs) ? verbs : [verbs] verbs_.forEach(verb => { @@ -138,7 +148,9 @@ class Acl { * @return Boolean */ can(user, verb, subject, ...args) { + subject = typeof subject === 'undefined' ? GlobalRule : subject const subjectName = this.subjectMapper(subject) + let rules = this.policies.get(subjectName) || this.rules.get(subjectName) if (typeof rules === 'undefined') { @@ -314,5 +326,3 @@ class Acl { return this } } - -export default Acl diff --git a/test/Acl.test.js b/test/Acl.test.js index c965645..9987ecf 100644 --- a/test/Acl.test.js +++ b/test/Acl.test.js @@ -16,6 +16,13 @@ describe('The basics', () => { expect(user.can).toBeDefined() }) + test('Global rules', () => { + const acl = new Acl() + acl.rule('purgeInactive', user => user.isAdmin) + expect(acl.can({isAdmin: true}, 'purgeInactive')).toBe(true) + expect(acl.can({isAdmin: false}, 'purgeInactive')).toBe(false) + }) + test('Cannot eat apples (no rule)', () => { const acl = new Acl() acl.mixin(User)