Skip to content

Commit

Permalink
feat: Add global rules
Browse files Browse the repository at this point in the history
References #1
  • Loading branch information
mblarsen committed Mar 31, 2018
1 parent 28001d3 commit 442ad55
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 31 deletions.
83 changes: 57 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
Expand Down Expand Up @@ -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]&lt;[string][15]> | [string][15])**
- `subject` **([Function][16] \| [Object][17] \| [string][15])**
- `test` **([Boolean][18] \| [Function][16])** =true (optional, default `true`)
- `verbs` **([Array][16]&lt;[string][17]> | [string][17])**
- `subject` **([Function][18] \| [Object][14] \| [string][17])** ?
- `test` **([Boolean][15] \| [Function][18])** =true (optional, default `true`)

Returns **[Acl][19]**

Expand Down Expand Up @@ -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]**

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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]&lt;([Function][16] \| [Object][17] \| [string][15])>**
- `subjects` **[Array][16]&lt;([Function][18] \| [Object][14] \| [string][17])>**
- `args` **...any** Any other param is passed into rule

Returns **any** Boolean
Expand All @@ -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]&lt;([Function][16] \| [Object][17] \| [string][15])>**
- `subjects` **[Array][16]&lt;([Function][18] \| [Object][14] \| [string][17])>**
- `args` **...any** Any other param is passed into rule

Returns **any** Boolean
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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]**

Expand All @@ -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]**

Expand All @@ -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]**

Expand Down Expand Up @@ -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
20 changes: 15 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -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}
*/
Expand All @@ -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>|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 => {
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -314,5 +326,3 @@ class Acl {
return this
}
}

export default Acl
7 changes: 7 additions & 0 deletions test/Acl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 442ad55

Please sign in to comment.