Gardien

CircleCIHex.pm Version

Simple, protocol based authorization, for Phoenix projects.

Gardien | Documentation

Installation

Add gardien to your list of dependencies in mix.exs:

def deps do
  [{:gardien, "~> 1.0.0"}]
end

Then run mix deps.get to fetch the dependencies.

Configuration

By default Gardien will try to extract user from conn.assigns using current_user key. In case you want to change this behaviour, you can configure user as follows:

config :gardien, user: :admin

or

config :gardien, user: {MyHelpers, :user}

where user is a function that takes conn as an argument, e.g:

defmodule MyHelpers do
  def user(conn) do
    # return current user
  end
end

Gardien comes with default handler that will raise Gardien.AuthorizationError in case user is not authorized to perform some action. You can overwrite default handler as follows:

config :gardien,
  unauthorized_handler: {MyHelpers, :unauthorized_handler}

where :unauthorized_handler is a function that takes conn and authorization context as arguments, e.g:

defmodule MyHelpers do
  def unauthorized_handler(conn, context) do
    # handle not authorized action
  end
end

Gardien.Policy protocol

Gardien.Policy protocol should be implemented for each domain model (resource) that needs to be authorized. This protocol defines authorize?(resource, action, user) function and is used by authorization functions to verify whether user is allowed to perform some action on a given resource. Gardien.Policy.authorize?/3 should return true or false.

Note: Gardien comes with a Gardien.Authorize module, that can be use-d in order to implement a more descriptive policy.

Gardien.Policy implementation example (with Gardien.Authorize):

defimpl Gardien.Policy, for: MyApplication.Post do
  use Gardien.Authorize

  def edit(resource, user) do
    user.id == resource.user_id
  end

  # ...
end

In case you’re building a closed system, where only logged in users are able to do anything, you can define your own Authorize as follows:

defmodule MyApplication.Authorize do
  defmacro __using__(_opts) do
    def authorize?(_resource, _action, user) when is_nil(user) do
      false
    end
    def authorize?(resource, action, user) do
      apply(__MODULE__, action, [resource, user])
    end
  end
end

If you want to implement a policy without a corresponding model, one way to do that would be to define Gardien.Policy implementation for Atom, e.g:

defimpl Gardien.Policy, for: Atom do
  def authorize?(:dashboard, :view, _user) do
    false
  end
end

Overview

Define policy:

defimpl Gardien.Policy, for: MyApplication.Post do
  use Gardien.Authorize

  def edit(post, user) do
    post.user_id == user.id
  end
end

Use in controller:

defmodule MyApplication.PostController do
  import Gardien.Controller

  def edit(conn, params) do
    post = Repo.get(Post, params["id"])

    authorize post, conn, fn ->
      render conn, "edit.html", post: post
    end
  end
end

Use as a plug:

defmodule MyApplication.PostController do
  # authorization plug expects `post` to be present is `conn.assigns`
  plug Gardien.Authorization, resource: :post

  def edit(conn, params) do
    render conn, "edit.html", post: post
  end
end

Use in view/template:

defmodule MyApplication.PostView do
  import Gardien.View, only: [authorize?: 3]
end

# in template
<%= if authorize?(post, :edit, @conn) do %>
  <%= link "Edit", to: post_path(@conn, :edit, post) %>
<% end %>