Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Get and Cache List of Roles + Helper Functions: has_role?/2 #7

Merged
merged 24 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
50cce5d
add dependency on HTTPoison and Jason to make HTTP requests for /appr…
nelsonic Sep 13, 2020
e05a58a
create get_approles/2 function to load list of roles for an app https…
nelsonic Sep 13, 2020
7c41353
use :ets to store roles list in cache https://github.com/dwyl/auth/is…
nelsonic Sep 14, 2020
5b719bb
create get_role_from_cache/1 for #1
nelsonic Sep 14, 2020
99ec7d3
implement has_role/2 (happy path) #1
nelsonic Sep 14, 2020
abeb6e1
add tests for unhappy path and refactor get_role_from_cache/1 to pass…
nelsonic Sep 14, 2020
ffebff5
mix format
nelsonic Sep 14, 2020
0aa6124
add test to confirm that has_role/2 works with integers too! e.g. has…
nelsonic Sep 14, 2020
1fb2056
bump version to 0.4.0 with latest functions #1
nelsonic Sep 14, 2020
90a16a8
implement has_role_any?/2 to check if person has any of listed roles #1
nelsonic Sep 14, 2020
7dcab44
tidy up docs to add ? to has_role?/2 and has_role_any?/2 #1
nelsonic Sep 14, 2020
1e9f126
add docs / usage example to README.md for #1
nelsonic Sep 14, 2020
012bc3d
Remove redundunt function call
th0mas Sep 15, 2020
c2a863c
Correct wrong function signature
th0mas Sep 15, 2020
efa5031
Remove func signature - I think this is easier
th0mas Sep 15, 2020
5a7147c
Set functions to private
th0mas Sep 15, 2020
87f3834
Return an explicit error
th0mas Sep 15, 2020
b2b1540
Log error when role not found to alert dev of typo or role.name issue…
nelsonic Sep 16, 2020
3f55f0e
add function defintion for has_role?/2 String, List so Plug.Conn is n…
nelsonic Sep 16, 2020
1ea682c
bump version to 0.5.0 as new fn insert_roles_into_ets/1 added
nelsonic Sep 16, 2020
25ebaea
has_role?2 and has_role_any?2 now both accept List of Integers as fir…
nelsonic Sep 16, 2020
6988e2b
allow atom as role or roles in has_role/2 and has_role_any?2 respecti…
nelsonic Sep 16, 2020
3a902ea
Clarify independence from auth & auth_plug in README.md https://githu…
nelsonic Sep 16, 2020
ad51cc1
tidy @doc comments for consistency
nelsonic Sep 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ rbac-*.tar
*.beam
/config/*.secret.exs
.elixir_ls/
.env
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: elixir
elixir:
- 1.10.2
- 1.10.4
otp_release:
- 22.1.8
- 23.0.3
env:
- MIX_ENV=test
script:
Expand Down
250 changes: 241 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# `rbac`

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

Expand All @@ -20,7 +20,9 @@ to specific data/features in your App(s).

## Why?

RBAC lets you easily manage roles and permissions in any application
You want an _easy_ way to restrict access to features fo 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.
Expand All @@ -29,7 +31,7 @@ Access Control List (ACL) based permissions systems.

## What?

The purpose of RBAC is to provide a framework
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).

Expand All @@ -39,7 +41,7 @@ to manage the permissions assigned to the people using the App(s).

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


## _How_?
Expand All @@ -52,21 +54,253 @@ Install by adding `rbac` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:rbac, "~> 0.1.0"}
{:rbac, "~> 0.5.0"}
]
end
```

API/Function reference available at
[https://hexdocs.pm/rbac](https://hexdocs.pm/rbac).
<br />

### Initialize Your Roles List (Cache)

In order 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:

```elixir
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:

```elixir
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:

```elixir
# 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`](https://github.com/dwyl/auth) App
and it's corresponding helper library
[`auth_plug`](https://github.com/dwyl/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 initalize it
with the following code:

```elixir
# 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.


<br />

### Usage

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

```elixir
# 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` or`Integer`
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:

```elixir
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`](https://github.com/dwyl/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:
```elixir
%{
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:

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

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

```elixir
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:

```elixir
RBAC.has_role?(conn, 2)
> true
```

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

```elixir
RBAC.has_role?(conn, 1)
> false
```

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

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

You can even _mix_ the type in the list (_though we don't recommend it..._):

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

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

```elixir
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:

```elixir
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](https://hexdocs.pm/rbac).

<!--
## Trouble Shooting

If your app does not have a valid `AUTH_API_KEY` you may see the following error:

```
Generated auth app
** (Mix) Could not start application auth: exited in: Auth.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (Protocol.UndefinedError) protocol Enumerable not implemented for "Internal Server Error" of type BitString. This protocol is implemented for the following type(s): Ecto.Adapters.SQL.Stream, Postgrex.Stream, DBConnection.PrepareStream, DBConnection.Stream, StreamData, IO.Stream, Map, Date.Range, List, GenEvent.Stream, HashSet, MapSet, Range, HashDict, Function, Stream, File.Stream
(elixir 1.10.4) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir 1.10.4) lib/enum.ex:141: Enumerable.reduce/3
(elixir 1.10.4) lib/enum.ex:3383: Enum.map/2
(rbac 0.4.0) lib/rbac.ex:69: RBAC.parse_body_response/1
(rbac 0.4.0) lib/rbac.ex:88: RBAC.init_roles_cache/2
(auth 1.2.4) lib/auth/application.ex:9: Auth.Application.start/2
(kernel 7.0) application_master.erl:277: :application_master.start_it_old/4
The command "mix ecto.setup" failed and exited with 1 during .
```

Simply follow the instructions to get your `AUTH_API_KEY` and export it as an environment variable.
-->

<br /><br />

## tl;dr > RBAC Knowledge Summary
Expand Down Expand Up @@ -100,8 +334,6 @@ An operation can only be completed
if the person attempting to complete the transaction
possesses the appropriate role.



## Recommended Reading

+ https://en.wikipedia.org/wiki/Role-based_access_control
Expand Down
Loading