Helper functions for Role Based Access Control (RBAC)


License
CNRI-Python-GPL-Compatible

Documentation

rbac

Role Based Access Control (RBAC) gives you a human-friendly way of controlling access to specific data/features in your App(s).

GitHub Workflow Status codecov.io Hex.pm Libraries.io dependency status docs contributions welcome HitCount

Why?

You want an easy way to restrict access to features for your Elixir/Phoenix App based on a sane model of roles. RBAC lets you easily manage roles and permissions in any application and see at a glance exactly which permissions a person has in the system. It reduces complexity over traditional Access Control List (ACL) based permissions systems.

What?

The purpose of RBAC is to provide a framework for application administrators and developers to manage the permissions assigned to the people using the App(s).

Who?

Anyone who is interested in developing secure applications used by many people with differing needs and permissions should learn about RBAC.

How?

Installation

Install by adding rbac to your list of dependencies in mix.exs:

def deps do
  [
    {:rbac, "~> 0.6.0"}
  ]
end

Initialize Your Roles List (Cache)

In order to use RBAC you need to initialize the in-memory cache with a list of roles.

Got your Own List of Roles?

If you prefer to manage your own list of roles you can simply supply your own list of roles e.g:

roles = [%{id: 1, name: "admin"}, %{id: 2, name: "subscriber"}]
RBAC.insert_roles_into_ets_cache(roles)

To initialize the list of roles once (at boot) for your Phoenix App, open the application.ex file of your project and locate the def start(_type, _args) do definition, e.g:

def start(_type, _args) do
  # List all child processes to be supervised
  children = [
    # Start the Ecto repository
    App.Repo,
    # Start the endpoint when the application starts
    {Phoenix.PubSub, name: App.PubSub},
    AppWeb.Endpoint
    # Starts a worker by calling: Auth.Worker.start_link(arg)
    # {Auth.Worker, arg},
  ]

  # See https://hexdocs.pm/elixir/Supervisor.html
  # for other strategies and supported options
  opts = [strategy: :one_for_one, name: App.Supervisor]
  Supervisor.start_link(children, opts)
end

Add the following code at the top of the start/2 function definition:

# initialize RBAC Roles Cache:
roles = [%{id: 1, name: "admin"}, %{id: 2, name: "subscriber"}]
RBAC.insert_roles_into_ets_cache(roles)

Using auth to Manage Roles?

RBAC is independent from our auth App and it's corresponding helper library auth_plug.

However if you want a ready-made list of universally applicable roles and an easy way to manage and create custom roles for your App, auth has you covered: https://dwylauth.herokuapp.com

Once you have exported your AUTH_API_KEY Environment Variable following these instructions: https://github.com/dwyl/auth_plug#2-get-your-auth_api_key-

You can source your list of roles and initialize it with the following code:

# initialize RBAC Roles Cache:
RBAC.init_roles_cache(
  "https://dwylauth.herokuapp.com",
  AuthPlug.Token.client_id()
)

AuthPlug.Token.client_id() expects the AUTH_API_KEY Environment Variable to be set.


Usage

Once you have added the initialization code, you can easily check that a person has a required role using the following code:

# role argument as String
RBAC.has_role?([2], "admin")
> true

# role argument as Atom
RBAC.has_role?([2], :admin)
> true

# second argument (role) as Integer
RBAC.has_role?([2], 2)
> true

The first argument is a List of role ids. The second argument (role) can either be an String, Atom orInteger corresponding to the name of the role or the id respectively. We prefer using String because its more developer/maintenance friendly. We can immediately see which role is required

Or if you want to check that the person has any role in a list of potential roles:

RBAC.has_role_any?([2,4,7], ["admin", "commenter"])
> true

RBAC.has_role_any?([2,4,7], [:admin, :commenter])
> true

Using rbac with auth_plug

If you are using auth_plug to handle checking auth in your App. It adds the person map to the conn.assigns struct. That means the person's roles are listed in: conn.assigns.person.roles

e.g:

%{
  app_id: 8,
  auth_provider: "github",
  email: "alex.mcawesome@gmail.com",
  exp: 1631721211,
  givenName: "Alex",
  id: 772,
  roles: "2"
}

For convenience, we allow the first argument of both has_role/2 and has_role_any?/2 to accept conn as the first argument:

RBAC.has_role?(conn, "admin")
> true

Check that the person has has any role in a list of potential roles:

RBAC.has_role_any?(conn, ["admin", "commenter"])
> true

We prefer to make our code as declarative and human-friendly as possible, hence the String role names. However both the role-checking functions also accept a list of integers, corresponding to the role.id of the required role, e.g:

RBAC.has_role?(conn, 2)
> true

If the person does not have the superadmin role, has_role?/2 will return false

RBAC.has_role?(conn, 1)
> false

Or supply a list of integers to has_role_any?/2 if you prefer:

RBAC.has_role_any?(conn, [1,2,3])
> true

You can even mix the type in the list (though we don't recommend it...):

RBAC.has_role_any?(conn, ["admin",2,3])
> true

We recommend picking one, and advise using strings for code legibility. e.g:

RBAC.has_role?(conn, "building_admin")

Is very clear which role is required. Whereas using an int (especially for custom roles) is a bit more terse:

RBAC.has_role?(conn, 13)

It requires the developer/code reviewer/maintainer to either know what the role is, or look it up in a list. Stick with String as your role names in your code.

API/Function reference available at https://hexdocs.pm/rbac.



tl;dr > RBAC Knowledge Summary

Each role granted just enough flexibility and permissions to perform the tasks required for their job, this helps enforce the principal of least privilege

The RBAC methodology is based on a set of three principal rules that govern access to systems:

  1. Role Assignment: Each transaction or operation can only be carried out if the person has assumed the appropriate role. An operation is defined as any action taken with respect to a system or network object that is protected by RBAC. Roles may be assigned by a separate party or selected by the person attempting to perform the action.

  2. Role Authorization: The purpose of role authorization is to ensure that people can only assume a role for which they have been given the appropriate authorization. When a person assumes a role, they must do so with authorization from an administrator.

  3. Transaction Authorization: An operation can only be completed if the person attempting to complete the transaction possesses the appropriate role.

Recommended Reading