Gardien
Simple, protocol based authorization, for Phoenix projects.
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: :adminor
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
endGardien.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
endOverview
Define policy:
defimpl Gardien.Policy, for: MyApplication.Post do
use Gardien.Authorize
def edit(post, user) do
post.user_id == user.id
end
endUse 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
endUse 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
endUse 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 %>