Pundit provides a set of helpers which guide you in leveraging regular Elixir methods to build a simple authorization system. This library is based heavily on Jonas Nicklas' Ruby project of the same name.
Simple Elixir functions are defined for a given struct and allow you to encapsulate authentication logic. You can use this code within a module that is an Ecto.Schema, but that's not necessary (Ecto isn't required). The action names are taken from the list of actions defined by Phoenix controllers.
To install Pundit, just add an entry to your mix.exs
:
def deps do
[
# ...
{:pundit, "~> 1.0"}
]
end
(Check Hex to make sure you're using an up-to-date version number.)
Here's a basic example, starting with a simple struct for a Post
. A module named Post.Policy
should be created to
encapsulate all of the access methods (Pundit
will automatically look for the <struct module>.Policy
module
to determine the module name to look at for access methods).
To declare an initial set of access functions (show?
, edit?
, delete?
, etc)
which all return false
(default safe!), just use
Pundit.DefaultPolicy
. You can then override the functions as needed
with the logic necessary to determine whether a user should be able to perform the given action. In this example, we only
determine whether a user can edit?
a post, leaving all other functions (like delete?
) to return the default of false
.
defmodule Post do
defstruct [:author, :title, :body, :comments]
defmodule Policy do
# This will initialize all the action functions, all of which return false
# by default. Override them individually to return true when they should,
# like edit? is overriden below.
use Pundit.DefaultPolicy
def edit?(post, user) do
user.name == post.author
end
end
end
post = %Post{author: "Snake Plissken"}
author = %{name: "Snake Plissken"}
# next line is same as Pundit.can?(post, author, :edit?)
# Pundit will just delegate to Post.Policy.edit?(post, user)
if Pundit.edit?(post, author) do
IO.puts("Can edit!")
end
if Pundit.delete?(post, author) do
IO.puts("This line should never be called")
end
# raise exception if user should be able to do a thing
Pundit.authorize!(post, author, :edit?)
You can also provide query scope for a struct (say, if you're using Ecto.Schema
) for a given user. For instance,
say our Post
was an Ecto
schema. Our function for scoping all Post
s to a specific User
could be to find all
Post
s that were authored by a user. For instance:
defmodule Post do
use Ecto.Schema
import Ecto.Query, only: [from: 2]
defmodule Policy do
use Pundit.DefaultPolicy
def scope(query, user) do
from post in query,
where: post.author_id == ^user.id
end
end
end
user = MyApp.Repo.get(User, 1)
posts = Pundit.scope(Post, user) |> Repo.all()
query = from p in Post, where: p.comment_count > 10
popular_posts = Pundit.scope(query, user) |> Repo.all()
See the docs for more examples.
To run tests:
$ mix test
Please report all issues on github.