@northscaler/rbac

1.0.0-pre.4 • Public • Published

rbac

A role-based access control supporting library

This library provides a means to declare policies governing access control to arbitrary securables based on roles and interrogate those policies to make access control decisions.

NOTE: In the context of this discussion & this module, the term "role" refers to a type, not an instance.

Security concepts

In any security decision, there are four fundamental things required:

  • Securable: the thing access to which is being governed.
  • Principal: the thing that is attempting to perform an Action on a Securable. This is sometimes also known as a "subject".
  • Action: the activity that is being attempted.
  • Access Control Entry: the thing that binds Securable, Principals and Actions together along with an access control strategy that determines whether the activity is allowed to take place. Access Control Entrys are also sometimes known as "permissions", but that implies a positive sense that breaks down when the security implementation supports explicit denials. Acess Control Entrys are often collected into access control lists (or ACLs). Common access control strategies are "permit" & "deny", but can also be algorithmic depending on context, like "permit only if it's sunny".

Mapping of security concepts to this library

In the context of this library, a Principal is represented by a named role as a string.

The base class in this library, AbstractRoleBasedAccessControlRepository, supports arbitrary Securables, but also provides a concrete implementation, MethodAccessControlRepository, that secures methods on JavaScript classes; the Securables for that implementation are, then, the methods themselves.

The notion of Action has not yet been made explicit in this library. In the case of MethodAccessControlRepository, the Action is to simply invoke the method. The library authors anticipate adding support for Actions in a future release.

The notion of an access control list is manifested as a security policy that you declare and pass to a repository's constructor. Each policy is a JavaScript Array of entries of literal Objects. Each entry in the policy is an Access Control Entry. Each entry can have a roles property and/or a strategy property, but it must have some representation of the Securable whose access control is being declared. Since roles in this implementation are Strings, the type of an entry's roles is a RegExp that is matched to role strings. An entry's strategy can be

  • the JavaScript literal true, to indicate that access is permitted,
  • the JavaScript literal false, to indicate that access is explicitly denied, or
  • a Function that takes contextual information and returns a Boolean indicating whether or not access is permitted.

In the absence of the roles property, the value is assumed to be all roles, or the regular expression /^.+$/.

In the absence of the strategy property, this library makes the conservative choice to deny, meaning the default strategy is false.

Each concrete implementation of a repository determines what property or properties comprise the identification of the Securable. In the case of MethodAccessControlRepository, the Securable is a JavaScript class method, so the repository expects two properties, classes & methods, both of which are expected to be RegExps and are matched against the values given in the repository's interrogation methods.

Capabilities

Each repository, once given a policy, allows the consumer to interrogate whether a role is allowed to access a Securable. The interrogative methods are as follows.

  • permits({role, securable, data}): returns true if the given role (which can also be an array of roles) is (are) allowed to access the securable given optional, arbitrary, contextual data, else returns false.
  • explicitlyDenies({role, securable, data}): returns true if the given role (or roles) is (are) explicitly denied the ability to access the securable given optional, arbitrary, contextual data, else returns false.

Note the subtle distinction between the two. Users of this library will almost always use permits; expicitlyDenies is intended for more subtle use cases.

Example

Let's take MethodAccessControlRepository as an example.

NOTE: this is a low-tech example. For a high-tech usage of this library that leverages JavaScript decorators, see the test should work as intended in an app in src/test/MethodAccessControlRepository.spec.js.

First, declare your policy. We want Administrators to be able to do anything, Tellers & Managers to be able to open Accounts, but only Managers can close high-value Accounts.

// in file security.js

module.exports = [{
  // Administrators can do anything
  roles: /^Administrator$/, // role name regex
  strategy: true,           // true means "permit", false means "explicitly deny"
  classes: /^.+$/,          // regex of class names
  methods: /^.+$/           // regex of method names
}, {
  // Tellers & Managers can open accounts
  roles: /^Teller|Manager$/,
  strategy: true,
  classes: /^Account$/,
  methods: /^open$/
}, {
  // Only Managers can close high-value accounts
  strategy: ({role, account}) => { // custom access control strategy function
    switch (role) {
      case 'Teller': return account.balance < 10000
      case 'Manager': return true
      default: return false
    }
  },
  classes: /^Account$/,
  methods: /^close$/
}]

Next, instantiate your repository with the policy.

// in file rbac.js

const { MethodAccessControlRepository } = require('@northscaler/rbac')
const acl = require('./security')

module.exports = new MethodAccessControlRepository(policy)

Now, you have a repository that you can query for access control decisions. To make use of it, you need to either manually code calls to it along your regular call paths, or use a slicker, aspect-oriented solution like JavaScript decorators to intercept method calls and make access control decisions.

Here's a simple example using manual, inline security calls.

// in file Account.js
const getCallerRoles = require('./getCallerRoles') // magic that gets the roles of the current caller
const rbac = require('./rbac') // from above

function checkSecurity({ clazz, method, data}) {
 if (!rbac.permits({role: getCallerRoles(), securable: {class: clazz, method}, data})) {
   throw new Error(`no roles among ${roles.join(',')} permitted to invoke ${clazz}.${method}`)
 }
}

class Account {
  static open({id, firstName, lastName}) {
    checkSecurity({clazz: 'Account', method: 'open'})
    return new Account({id, firstName, lastname})  
  }

  constructor({id, firstName, lastName}) {
    this.id = id
    this.firstName = firstName
    this.lastName = lastName
    this.balance = 0  
  }

  close() {
    checkSecurity({clazz: 'Account', method: 'close', data: this}) // `this` will show up as `account` in strategy above
    // ...logic to close account
  }
}

Package Sidebar

Install

npm i @northscaler/rbac

Weekly Downloads

1

Version

1.0.0-pre.4

License

MIT

Unpacked Size

1.16 MB

Total Files

47

Last publish

Collaborators

  • ballista-admin
  • npm_northscaler
  • npm_scispike
  • victorynap