Packages
troll_bridge
0.1.0
Permission verification library with Phoenix/LiveView direct implementation - The troll must give you permission to pass through the bridge.
Current section
Files
Jump to
Current section
Files
lib/troll_bridge.ex
defmodule TrollBridge do
@moduledoc """
Helps to manage roles and permissions directly on controllers/live_view with just a few config to implement
This library uses some concepts:
- Action: basically it's just the name of the action that you want to run, a basic concept it's that every `crud`
function it's an action
- Permission Scope: it's the name of system area or whatever you want to define as a group of functions that you want
to lock with an action, a basic concept it's the controller it's an scope
- Roles: it's the collection of scopes and actions that has the access schema of the user
Example:
```
%{
user: %{
page: [:index, :show]
}
}
```
## Usage;
`troll_bridge` directly helps to implement to your phoenix controller or live_view screens.
#### Add the configuration
You need to create a module that uses the behaviour `TrollBridge.Behaviour`
```elixir
# troll_bridge_example/config/config.exs
config :troll_bridge, config: TrollBridgeExample.TrollConfig
# troll_bridge_example/lib/troll_bridge_example/troll_config.ex
defmodule TrollBridgeExample.TrollConfig do
@behaviour TrollBridge.Behaviour
@impl TrollBridge.Behaviour
def actions do
[:index, :show]
end
@impl TrollBridge.Behaviour
def scopes do
[:page, :page_alt]
end
@impl TrollBridge.Behaviour
def user_roles(_conn_or_socket) do
# here use the connection to get the session user and then from user get the assigned roles
[:user]
end
@impl TrollBridge.Behaviour
def user_permissions(_conn_or_socket) do
# here use the connection to get the session user and get the permissions of the assigned roles
%{page: [:index]}
end
end
```
#### The implementation
- Add `use TrollBridge.PhoenixController` or `use TrollBridge.LiveView`
- Use the `permissions` macro to define a permission scope to the entire module (optional)
- Add the module attribute `@action :index` to your function, you can specify the permission scope
```elixir
defmodule TrollBridgeExampleWeb.PageController do
use TrollBridgeExampleWeb, :controller
use TrollBridge.PhoenixController
permissions(:page)
redirect_on_failure_url("/api/failure")
@action :index
def index(conn, _params) do
conn
|> json(%{success: true, call: :index})
end
@action :show
def show(conn, _params) do
conn
|> json(%{success: true, call: :show})
end
@action page_alt: :show
def show_alt(conn, _params) do
conn
|> json(%{success: true, call: :show})
end
def failure(conn, _params) do
conn
|> json(%{forbidden: true})
end
end
```
> The permission and the action must be defined on the scopes and actions on the implementation of `TrollBridge.Behaviour`
"""
@type role_permissions() :: %{
atom() => [atom()]
}
@type allowed_actions :: [atom()]
def allowed_actions, do: get_config().actions()
@type allowed_actions_string :: [String.t()]
def allowed_actions_string, do: allowed_actions() |> Enum.map(&to_string/1)
@spec allowed?(role_permissions(), atom(), atom()) :: boolean()
def allowed?(permissions, scope, action) do
if Enum.member?(allowed_actions(), action) do
permissions
|> Map.get(scope, [])
|> then(fn actions ->
Enum.member?(actions, :all) or Enum.member?(actions, action)
end)
else
false
end
end
def access_allowed?(conn_or_socket, [{scope_or_role, action}]) do
cond do
Enum.member?(get_config().scopes(), scope_or_role) ->
conn_or_socket
|> get_config().user_permissions()
|> allowed?(scope_or_role, action)
true ->
conn_or_socket
|> get_config().user_roles()
|> Enum.member?(scope_or_role)
end
end
def access_allowed?(conn_or_socket, action) do
case scope(conn_or_socket) do
nil ->
role = role(conn_or_socket)
conn_or_socket
|> get_config().user_roles()
|> Enum.member?(role)
scope ->
conn_or_socket
|> get_config().user_permissions()
|> allowed?(scope, action)
end
end
defp scope(conn_or_socket) do
conn_or_socket
|> get_module()
|> then(& &1.troll_bridge_scope())
end
defp role(conn_or_socket) do
conn_or_socket
|> get_module()
|> then(& &1.troll_bridge_role())
end
defp get_module(%{private: private}),
do: Map.get(private, :phoenix_controller)
defp get_module(%{view: view}),
do: view
def get_config, do: Application.get_env(:troll_bridge, :config)
end