@northscaler/acl

Security via access control list (ACL)

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
@northscaler/acl
1.4.12 years ago4 years agoMinified + gzip package size for @northscaler/acl in KB

Readme

@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.
This library defines a minimal set of primitive actions, but you can define your own.
  • access control entry: the binding of the principal, securable and action together along with the "permitted" (or "denied") boolean, or some other strategy.
Some systems call this a "permission", a "right" or a "grant" in the positive sense, and a "denial", an "antipermission", or a "negative permission" in the negative sense. We use the more general term "access control entry", often abbreviated ACE (similar to ACL), which can mean either a permission or a denial.
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 in src/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)

  1. Creates a new access control list.
  2. A principal.
This could be either an id referring to a principal as in this example, or an actual principal object. Up to you.
  1. A securable.
This could be either an id referring to a securable as in this example, or an actual securable object. Up to you.
  1. An action.
This could be anything you want, from one of the primitive actions, to a method name. Again, up to you.
  1. Instructs the ACL to permit the given principal the ability to take the given action against the given securable.
  2. Interrogates the ACL to ensure that the permission took place correctly.
  3. Instructs the ACL to remove the permission that was previously given.
Note that this is not an explicit denial; it is only the removal of the permission.
  1. Interrogates the ACL to ensure that the removal of the permission took place correctly.
  2. Instructs the ACL to explicitly deny from given principal the ability to take the given action against the given securable.
  3. Interrogates the ACL to ensure that the denial took place correctly.
  4. Instructs the ACL to remove the denial that was previously given.
  5. 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

  1. Declare a strategy class.
The only requirements are that it has permits & denies methods.
  1. Create a parameterized constructor so that strategy class is more flexible.
  2. Define the permission logic.
We'll assume that the only securable type being passed in is an Account.
  1. In this case, principal sally is always permitted to close any account.
  2. john can close medium-valued accounts.
  3. Everyone can close low-valued accounts.
  4. 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.
Here, we're only using the principal & action to make the determination.
  1. 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.
Even though he's effectively permitted the ability to close a low-value account by virtue of the permits method, we're vetoing that capability here, because a single denial vetoes all permits.
  1. No one else is explicitly denied the ability to close an account.