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 endend
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 endend
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]