Building off of Chris Mccord you can actually do this without taking up a lot of space in the controller. Like this:
defmodule MyAppWeb.CategoryController do
use MyAppWeb, :controller
use MyApp.Helpers.Connection
plug :authorize_crud, %{action: :read, permissions: [:can_access_categories]} when action in [:index, :show]
plug :authorize_crud, %{action: :create, permissions: [:can_create_categories]} when action in [:new, :create]
plug :authorize_crud, %{action: :update, permissions: [:can_update_categories]} when action in [:edit, :update]
def index(conn, _params) do
conn
end
def new(conn, _params) do
conn
end
def create(conn, %{"category" => category_params}) do
conn
end
def update(conn, %{"id" => id, "category" => category_params}) do
conn
end
end
defmodule MyApp.Helpers.Connection do
@spec authorize(%Plug.Conn{}, list(), list()) :: %Plug.Conn{}
def authorize(conn, user_permissions \\ [], required_permissions \\ []) do
import Plug.Conn
up = user_permissions |> MapSet.new()
rp = required_permissions |> MapSet.new()
case MapSet.subset?(rp, up) do
true ->
conn
false ->
conn
|> put_status(404)
|> Phoenix.Controller.render(MyAppWeb.ErrorView, "404.html", %{layout: false})
|> halt()
end
end
defmacro __using__(_) do
quote do
def authorize_crud(conn, opts = %{action: :read, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :create, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :update, permissions: permissions}) do
check(conn, permissions)
end
def authorize_crud(conn, opts = %{action: :destroy, permissions: permissions}) do
check(conn, permissions)
end
def check(conn, permissions) do
user = conn.assigns.current_user |> Repo.preload(:role)
MyApp.Helpers.Connection.authorize(conn, Accounts.list_permissions(user), permissions)
end
end
end
end
Notes:
- When we use
use Myapp.Helpers.Connection
we get to use the imported function without a module name, this allows us to use it in the plug.
- The code
defmacro __using__(_)
is required because You have to include the using macro and put all the code that should be compiled into the using module in there
- The usage in my case is authorization functionality to restrict user access to certain controller actions. The
authorize/3
function serves to return a true or false if the user has the required requirements.
- Inside of the defmacro we are overriding authorize_crud with pattern matching from what comes in from the plug call.
- An ideal list of data for
Accounts.list_permissions(user)
is
[:can_access_categories,:can_create_categories]
- The permissions coming in to match are whatever come in as
permissions
in the plug. So
[:can_access_categories]