Permit.Phoenix.Controller behaviour (permit_phoenix v0.3.0)

View Source

Injects authorization plug (Permit.Phoenix.Plug), allowing to provide its options either directly in options of use, or as overridable functions.

Example:

# my_app_web.ex
def controller do
  use Permit.Phoenix.Controller,
    authorization_module: MyApp.Authorization,
    fallback_path: "/unauthorized"
end

# your controller module
defmodule MyAppWeb.PageController do
  use MyAppWeb, :live_view

  @impl true
  def resource_module, do: MyApp.Item

  # Override default action groupings if needed
  @impl true
  def action_grouping do
    %{
      new: [:create],
      index: [:read],
      show: [:read],
      edit: [:update],
      create: [:create],
      update: [:update],
      delete: [:delete]
    }
  end

  # Override singular actions if needed
  @impl true
  def singular_actions do
    [:show, :edit, :new, :delete, :update]
  end
end

Summary

Callbacks

Defines the action grouping schema for this controller. This can be overridden in individual controllers to customize the action mapping.

Configures the controller with the application's authorization configuration.

Creates the basis for an Ecto query constructed by Permit.Ecto based on controller action, resource module, subject (typically :current_user) and controller params.

Allows opting out of using Permit for given controller actions.

If handle_unauthorized/2 is not defined, sets the fallback path to which the user is redirected on authorization failure.

Retrieves the current user from conn as the authorization subject. Defaults to conn.assigns[:current_user].

Post-processes an Ecto query constructed by Permit.Ecto based on controller action, resource module, subject (typically :current_user) and controller params.

Called when authorization on an action or a loaded record is not granted. Must halt conn after rendering or redirecting.

Sets the name of the ID param that will be used for preloading a record for authorization.

Sets the name of the field that contains the resource's ID which should be looked for.

If Permit.Ecto is not used, it allows defining a loader function that loads a record or a list of records, depending on action type (singular or plural).

Declares which actions in the controller are to use Permit's automatic preloading and authorization in addition to defaults: [:show, :edit, :update, :delete, :index].

Declares the controller's resource module. For instance, when Phoenix and Ecto is used, typically for an ArticleController the resource will be an Article Ecto schema.

Defines which actions are considered singular (operating on a single resource). This can be overridden in individual controllers to customize the singular actions.

Callbacks

action_grouping()

@callback action_grouping() :: map()

Defines the action grouping schema for this controller. This can be overridden in individual controllers to customize the action mapping.

authorization_module()

@callback authorization_module() :: Permit.Types.authorization_module()

Configures the controller with the application's authorization configuration.

Example

@impl Permit.Phoenix.Controller
def authorization_module, do: MyApp.Authorization

# Requires defining an authorization configuration module
defmodule MyApp.Authorization, do:
  use Permit, permissions_module: MyApp.Permissions

base_query(resolution_context)

(optional)
@callback base_query(Permit.Types.resolution_context()) :: Ecto.Query.t()

Creates the basis for an Ecto query constructed by Permit.Ecto based on controller action, resource module, subject (typically :current_user) and controller params.

Typically useful when using nested resource routes. In an action routed like /users/:user_id/posts/:id, you can use the base_query/1 callback to filter records by user_id, while filtering by id itself will be applied automatically (the name of the ID parameter can be overridden with the c:id_).

Example

defmodule MyApp.CommentController do
  use Permit.Phoenix.Controller,
    authorization_module: MyApp.Authorization
    resource_module: MyApp.Blog.Comment

  @impl true
  def base_query(%{
    action: :index,
    params: %{"article_id" => article_id}
  }) do
    MyApp.CommentQueries.by_article_id(article_id)
  end
end

except()

(optional)
@callback except() :: [Permit.Types.action_group()]

Allows opting out of using Permit for given controller actions.

Defaults to [], thus by default all actions are guarded with Permit.

fallback_path(action_group, conn)

(optional)
@callback fallback_path(Permit.Types.action_group(), Permit.Phoenix.Types.conn()) ::
  binary()

If handle_unauthorized/2 is not defined, sets the fallback path to which the user is redirected on authorization failure.

Defaults to /.

fetch_subject(conn)

(optional)
@callback fetch_subject(Permit.Phoenix.Types.conn()) :: Permit.Types.subject()

Retrieves the current user from conn as the authorization subject. Defaults to conn.assigns[:current_user].

Example

@impl true
def fetch_subject(%{assigns: assigns}) do
  assigns[:user]
end

finalize_query(t, resolution_context)

(optional)
@callback finalize_query(Ecto.Query.t(), Permit.Types.resolution_context()) ::
  Ecto.Query.t()

Post-processes an Ecto query constructed by Permit.Ecto based on controller action, resource module, subject (typically :current_user) and controller params.

Typically useful when using nested resource routes. In an action routed like /users/:user_id/posts/:id, you can use the base_query/1 callback to filter records by user_id, while filtering by id itself will be applied automatically (the name of the ID parameter can be overridden with the c:id_).

Example

defmodule MyApp.CommentController do
  use Permit.Phoenix.Controller,
    authorization_module: MyApp.Authorization
    resource_module: MyApp.Blog.Comment

  # just for demonstration - please don't do it directly in controllers
  import Ecto.Query

  @impl true
  def finalize_query(query, %{
    action: :index,
  }) do
    query
    |> preload([c], [:user])
  end
end

handle_not_found(conn)

(optional)
@callback handle_not_found(Permit.Phoenix.Types.conn()) :: Permit.Phoenix.Types.conn()

handle_unauthorized(action_group, conn)

(optional)

Called when authorization on an action or a loaded record is not granted. Must halt conn after rendering or redirecting.

Example

defmodule MyApp.CommentController do
  use Permit.Phoenix.Controller,
    authorization_module: MyApp.Authorization
    resource_module: MyApp.Blog.Comment

  @impl true
  def handle_unauthorized(action, conn) do
    case get_format(conn) do
      "json" ->
        # render a 4xx JSON response

      "html" ->
        # handle HTML response, e.g. redirect
    end
  end
end

id_param_name(action_group, conn)

@callback id_param_name(Permit.Types.action_group(), Permit.Phoenix.Types.conn()) ::
  binary()

Sets the name of the ID param that will be used for preloading a record for authorization.

Defaults to "id". If the route contains a different name of the record ID param, it should be changed accordingly.

id_struct_field_name(action_group, conn)

@callback id_struct_field_name(Permit.Types.action_group(), Permit.Phoenix.Types.conn()) ::
  atom()

Sets the name of the field that contains the resource's ID which should be looked for.

Defaults to :id. If the record's ID (usually a primary key) is in a different field, then it should be changed accordingly.

loader(resolution_context)

(optional)
@callback loader(Permit.Types.resolution_context()) :: Permit.Types.object() | nil

If Permit.Ecto is not used, it allows defining a loader function that loads a record or a list of records, depending on action type (singular or plural).

Example

@impl true
def loader(%{action: :index, params: %{page: page}}),
  do: ItemContext.load_all(page: page)

def loader(%{action: :show}, params: %{id: id}),
  do: ItemContext.load(id)

preload_actions()

(optional)
@callback preload_actions() :: [Permit.Types.action_group()]

Declares which actions in the controller are to use Permit's automatic preloading and authorization in addition to defaults: [:show, :edit, :update, :delete, :index].

Defaults to [], which means that [:show, :edit, :update, :delete, :index] and no other actions will use preloading.

resource_module()

(optional)
@callback resource_module() :: Permit.Types.resource_module()

Declares the controller's resource module. For instance, when Phoenix and Ecto is used, typically for an ArticleController the resource will be an Article Ecto schema.

This resource module, along with the controller action name, will be used for authorization checks before each action.

If Permit.Ecto is used, this setting selects the Ecto schema which will be used for automatic preloading a record for authorization.

Example

defmodule MyApp.ArticleController do
  use Permit.Phoenix.Controller

  def authorization_module, do: MyApp.Authorization

  def resource_module, do: MyApp.Article

  # Alternatively, you can do the following:

  use Permit.Phoenix.Controller,
    authorization_module: MyApp.Authorization,
    resource_module: MyApp.Blog.Article
end

singular_actions()

@callback singular_actions() :: [atom()]

Defines which actions are considered singular (operating on a single resource). This can be overridden in individual controllers to customize the singular actions.

unauthorized_message(action_group, conn)

(optional)
@callback unauthorized_message(Permit.Types.action_group(), Permit.Phoenix.Types.conn()) ::
  binary()

Functions

handle_not_found(conn, opts)

unauthorized_message(action, conn, opts)