Canary.Plugs (canary v2.0.0-dev)

Plug functions for loading and authorizing resources for the current request.

The plugs all store data in conn.assigns (in Phoenix applications, keys in conn.assigns can be accessed with @key_name in templates)

In order to use the plug functions, you must import Canary.Plugs.

You must also specify the Ecto repo to use in your configuration:

config :canary, repo: Project.Repo

If you wish, you may also specify the key where Canary will look for the current user record to authorize against:

config :canary, current_user: :some_current_user

You can specify a error handler module (in this case, Helpers) to be called when an action is unauthorized like so:

config :canary, error_handler: Helpers

Module should implement the Canary.ErrorHandler behaviour.

Canary will pass the conn to the handler function.

Summary

Functions

Authorize the current user against the calling controller.

Authorize the current user for the given resource.

Authorize the given resource and then load it if authorization succeeds.

Load the given resource.

Functions

authorize_controller(conn, opts)

@spec authorize_controller(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Authorize the current user against the calling controller.

In order to use this function,

  1. conn.assigns[Application.get_env(:canary, :current_user, :current_user)] must be an ecto struct representing the current user

  2. conn.private must be a map (this should not be a problem unless you explicitly modified it)

authorize_controller checks for the name of the current controller in one of the following places

  1. :phoenix_controller in conn.private
  2. :canary_controller in conn.assigns

In case you are not using phoenix, make sure you set the controller name in the conn.assigns Note that in case neither of :phoenix_controller or :canary_controller are found the requested authorization won't necessarily fail, rather it will trigger a .can? function with a nil controller

If authorization succeeds, sets conn.assigns.authorized to true.

If authorization fails, sets conn.assigns.authorized to false.

Optional opts:

  • :only - Specifies which actions to authorize
  • :except - Specifies which actions for which to skip authorization
  • :unauthorized_handler - Specify a handler function to be called if the action is unauthorized

Examples:

plug :authorize_controller

plug :authorize_controller, only: [:index, :show]

plug :authorize_controller, except: [:destroy]

authorize_resource(conn, opts)

@spec authorize_resource(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Authorize the current user for the given resource.

In order to use this function,

  1. conn.assigns[Application.get_env(:canary, :current_user, :current_user)] must be an ecto struct representing the current user

  2. conn.private must be a map (this should not be a problem unless you explicitly modified it)

If authorization succeeds, sets conn.assigns.authorized to true.

If authorization fails, sets conn.assigns.authorized to false.

For the :index, :new, and :create actions, the resource in the Canada.Can implementation should be the module name of the model rather than a struct. A struct should be used instead of the module name only if the :persisted key is used and you want to override the default authorization behavior. This can be useful when dealing with nested resources.

For example:

use

  def can?(%User{}, :index, Post), do: true

instead of

  def can?(%User{}, :index, %Post{}), do: true

or

use

  def can?(%User{id: user_id}, :index, %Post{user_id: user_id}), do: true

if you are dealing with a nested resource, such as, "/post/post_id/comments"

You can specify additional actions for which Canary will authorize based on the model name, by passing the non_id_actions opt to the plug.

For example,

  plug :authorize_resource, model: Post, non_id_actions: [:find_by_name]

Required opts:

  • :model - Specifies the module name of the model to authorize access to

Optional opts:

  • :only - Specifies which actions to authorize
  • :except - Specifies which actions for which to skip authorization
  • :preload - Specifies association(s) to preload
  • :id_name - Specifies the name of the id in conn.params, defaults to "id"
  • :id_field - Specifies the name of the ID field in the database for searching :id_name value, defaults to "id".
  • :persisted - Specifies the resource should always be loaded from the database, defaults to false
  • :unauthorized_handler - Specify a handler function to be called if the action is unauthorized

Examples:

plug :authorize_resource, model: Post

plug :authorize_resource, model: User, preload: :posts

plug :authorize_resource, model: User, only: [:index, :show], preload: :posts

plug :load_resource, model: Post, id_name: "post_id", only: [:index], persisted: true, preload: :comments

plug :load_resource, model: Post, id_name: "slug", id_field: "slug", only: [:show], persisted: true

load_and_authorize_resource(conn, opts)

Authorize the given resource and then load it if authorization succeeds.

If the resource cannot be loaded or authorization fails, conn.assigns.resource_name is set to nil.

The result of the authorization (true/false) is assigned to conn.assigns.authorized.

Also, see the documentation for load_resource/2 and authorize_resource/2.

Required opts:

  • :model - Specifies the module name of the model to load resources from

Optional opts:

  • :as - Specifies the resource_name to use
  • :only - Specifies which actions to authorize
  • :except - Specifies which actions for which to skip authorization
  • :preload - Specifies association(s) to preload
  • :id_name - Specifies the name of the id in conn.params, defaults to "id"
  • :id_field - Specifies the name of the ID field in the database for searching :id_name value, defaults to "id".
  • :unauthorized_handler - Specify a handler function to be called if the action is unauthorized
  • :not_found_handler - Specify a handler function to be called if the resource is not found

Note: If both an :unauthorized_handler and a :not_found_handler are specified for load_and_authorize_resource, and the request meets the criteria for both, the :unauthorized_handler will be called first.

Examples:

plug :load_and_authorize_resource, model: Post

plug :load_and_authorize_resource, model: User, preload: :posts, as: :the_user

plug :load_and_authorize_resource, model: User, only: [:index, :show], preload: :posts, as: :person

plug :load_and_authorize_resource, model: User, except: [:destroy]

plug :load_and_authorize_resource, model: Post, id_name: "slug", id_field: "slug", only: [:show], persisted: true

load_resource(conn, opts)

@spec load_resource(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()

Load the given resource.

Load the resource with id given by conn.params["id"] (or conn.params[opts[:id_name]] if opts[:id_name] is specified) and ecto model given by opts[:model] into conn.assigns.resource_name.

resource_name is either inferred from the model name or specified in the plug declaration with the :as key. To infer the resource_name, the most specific(right most) name in the model's module name will be used, converted to underscore case.

For example, load_resource model: Some.Project.BlogPost will load the resource into conn.assigns.blog_post

If the resource cannot be fetched, conn.assigns.resource_name is set to nil.

By default, when the action is :index, all records from the specified model will be loaded. This can be overridden to fetch a single record from the database by using the :persisted key.

Currently, :new and :create actions are ignored, and conn.assigns.resource_name will be set to nil for these actions. This can be overridden to fetch a single record from the database by using the :persisted key.

The :persisted key can override how a resource is loaded and can be useful when dealing with nested resources.

Required opts:

  • :model - Specifies the module name of the model to load resources from

Optional opts:

  • :as - Specifies the resource_name to use
  • :only - Specifies which actions to authorize
  • :except - Specifies which actions for which to skip authorization
  • :preload - Specifies association(s) to preload
  • :id_name - Specifies the name of the id in conn.params, defaults to "id"
  • :id_field - Specifies the name of the ID field in the database for searching :id_name value, defaults to "id".
  • :persisted - Specifies the resource should always be loaded from the database, defaults to false
  • :required - Same as :persisted but with not found handler - even for :index, :new or :create action
  • :not_found_handler - Specify a handler function to be called if the resource is not found

Examples:

plug :load_resource, model: Post

plug :load_resource, model: User, preload: :posts, as: :the_user

plug :load_resource, model: User, only: [:index, :show], preload: :posts, as: :person

plug :load_resource, model: User, except: [:destroy]

plug :load_resource, model: Post, id_name: "post_id", only: [:new, :create], persisted: true

plug :load_resource, model: Post, id_name: "slug", id_field: "slug", only: [:show], persisted: true