@northscaler/acl
This library allows you to maintain security information in access control lists (ACLs).There are four elements required in the determination of access:
- principal: The actor, user or system attempting to perform some action on a securable.
- securable: The thing being secured.
- action: The action being performed on a securable.
- access control entry: the binding of the principal, securable and action together along with the "permitted" (or "denied") boolean, or some other strategy.
The primary export of this module is a class called
Acl
, which has interrogation methods permits
& denies
, as well as mutating methods like permit
, unpermit
, deny
, undeny
, or, the more general secure
& unsecure
methods.>NOTE: In this implementation, a single denial vetoes any number of permissions, and the absence of any permissions denies.
It supports declarative or static security algorithms (think "permitted" or "denied" as a simple boolean), as well as algorithmic or dynamic security algorithms (think "permitted if today is a weekday", "denied if the balance is greater than 10000", or similar).
TL;DR
See the tests insrc/test/unit/Acl.spec.js
for usage.Simple, declarative strategy example
This is simply one of the tests.const acl = new Acl() // 1
const principal = uuid() // 2
const securable = uuid() // 3
const action = uuid() // 4
acl.permit({ principal, securable, action }) // 5
expect(acl.permits({ principals: principal, actions: action, securable })) // 6
.to.equal(true)
expect(acl.denies({ principals: principal, actions: action, securable }))
.to.equal(false)
acl.unpermit({ principal, securable, action }) // 7
expect(acl.permits({ principals: principal, actions: action, securable })) // 8
.to.equal(false)
expect(acl.denies({ principals: principal, actions: action, securable }))
.to.equal(false)
acl.deny({ principal, securable, action }) // 9
expect(acl.permits({ principals: principal, actions: action, securable })) // 10
.to.equal(false)
expect(acl.denies({ principals: principal, actions: action, securable }))
.to.equal(true)
acl.undeny({ principal, securable, action }) // 11
expect(acl.permits({ principals: principal, actions: action, securable })) // 12
.to.equal(false)
expect(acl.denies({ principals: principal, actions: action, securable }))
.to.equal(false)
- Creates a new access control list.
- A principal.
- A securable.
- An action.
- Instructs the ACL to permit the given principal the ability to take the given action against the given securable.
- Interrogates the ACL to ensure that the permission took place correctly.
- Instructs the ACL to remove the permission that was previously given.
- Interrogates the ACL to ensure that the removal of the permission took place correctly.
- Instructs the ACL to explicitly deny from given principal the ability to take the given action against the given securable.
- Interrogates the ACL to ensure that the denial took place correctly.
- Instructs the ACL to remove the denial that was previously given.
- Interrogates the ACL to ensure that the undenial took place correctly.
Example with dynamic strategy
This is taken from the test 'should work with a custom strategy'. It shows that you can use arbitrarily complex logic to make access control decisions. This example uses individual person's names, but they could just as easily be role type names. The goal is to ensure that only the right principals can close securables with balances (like accounts), so the action is'close'
.const sally = 'sally'
const john = 'john'
const felix = 'felix'
const close = 'close'
class Strategy { // 1
constructor (hiThreshold, loThreshold) { // 2
this.hiThreshold = hiThreshold
this.loThreshold = loThreshold
}
permits ({ principal, action, securable }) { // 3
switch (action) {
case close:
switch (principal) {
case sally:
return true // 4
case john:
return securable.balance < this.hiThreshold // 5
default:
return securable.balance < this.loThreshold // 6
}
}
}
denies ({ principal, action, securable }) { // 7
switch (action) {
case close:
switch (principal) {
case felix: // 8
return securable instanceof Account
default: // 9
return false
}
}
}
}
// see rest of test for assertions
- Declare a strategy class.
permits
& denies
methods.- Create a parameterized constructor so that strategy class is more flexible.
- Define the permission logic.
Account
.- In this case, principal
sally
is always permitted toclose
any account. john
can close medium-valued accounts.- Everyone can close low-valued accounts.
- This is the other method required by an access control strategy to determine if a principal is explicitly denied from taking the given action against the given securable.
- Some time ago, we must've determined that
felix
is unfortunately prone to hitting "enter" before thinking things through, so we have effectively barred him from closing any accounts.
permits
method, we're vetoing that capability here, because a single denial vetoes all permits.- No one else is explicitly denied the ability to close an account.