Minimal example

Policy definition

Policy module

defmodule MyApp.Policy do
  use LetMe.Policy

  object :article do
    action :create do
      allow role: :writer
    end
  end
end

Check module

defmodule MyApp.Policy.Checks do
  def role(%MyApp.User{role: role}, _object, role), do: true
  def role(_, _, _), do: false
end

Context module

defmodule MyApp.Blog do
  alias MyApp.Blog.Article
  alias MyApp.Policy

  def create_article(params, %MyApp.User{} = current_user) do
    with :ok <- Policy.authorize(:article_create, current_user) do
      %Article{}
      |> Article.changeset(params)
      |> Repo.insert()
    end
  end
end

Check examples

Check without options

Policy module

object :article do
  action :create do
    allow :writer
  end
end

Check implementation

def writer(%MyApp.User{role: :writer}, _object), do: true
def writer(_, _), do: false

A check written as a bare atom is called with two arguments.

Check with options

Policy module

object :article do
  action :delete do
    allow trust_level: 50
  end
end

Check implementation

def trust_level(%MyApp.User{trust_level: actual_level}, _, required_level)
  when actual_level >= required_level,
  do: true

def trust_level(_, _, _), do: false

A check written as a {name, option} tuple is called with three arguments.

Check that depends on the object

Policy module

object :article do
  action :update do
    allow :own_resource
  end
end

Check implementation

def own_resource(%MyApp.User{id: user_id}, %{user_id: user_id}), do: true
def own_resource(_, _), do: false

Rule examples

Multiple actions with the same rules

object :article do
  action [:create, :update, :delete] do
    allow :admin
  end
end

Combine checks with AND

action :create do
  allow [:two_fa_enabled, role: :writer]
end

Combine checks with OR

action :create do
  allow role: :admin
  allow role: :writer
end

Conditionally allow with exception

action :create do
  allow :is_admin
  deny :is_suspended
end

Always allow

action :read do
  allow true
end

Always deny

action :read do
  deny true
end

Always allow with exception

action :read do
  allow true
  deny :user_is_suspended
end

Add description

action :create do
  desc "allows a user to create a new article"
  allow role: :writer
end

Pre-hooks

Without options

Policy module

object :article do
  action :create do
    pre_hooks :preload_roles

    allow role: :admin
    allow role: :editor
    allow role: :writer
  end
end

Check module

def role(%MyApp.User{roles: roles}, _object, role) do
  Enum.any?(roles, & &1.id == role)
end

def role(_, _, _), do: false

def preload_roles(subject, object) do
  {MyApp.Repo.preload(subject, [:roles]), object}
end

Every option except :error that you pass to an authorize function is appended to the hook arguments. A hook registered without options is called with three arguments as soon as the caller passes any option.

With options

Options are only accepted in the three-element form. The module has to be named even when the hook is in the check module.

Policy module

object :article do
  action :create do
    pre_hooks {MyApp.Policy.Checks, :preload_roles, force: true}

    allow role: :admin
    allow role: :editor
    allow role: :writer
  end
end

Check module

def role(%MyApp.User{roles: roles}, _object, role) do
  Enum.any?(roles, & &1.id == role)
end

def role(_, _, _), do: false

def preload_roles(subject, object, opts) do
  {MyApp.Repo.preload(subject, [:roles], opts), object}
end

From a different module

object :article do
  action :create do
    pre_hooks {MyApp.Policy.Prehooks, :preload_roles}

    allow role: :admin
    allow role: :editor
    allow role: :writer
  end
end

Multiple pre-hooks

object :article do
  action :create do
    pre_hooks [:preload_roles, :role_list_to_role_id_list]

    allow role: :admin
    allow role: :editor
    allow role: :writer
  end
end

Metadata

object :article do
  action :create do
    allow role: :admin
    allow role: :editor
    allow role: :writer
    
    metadata :gql_exclude, true
    metadata :desc_ja, "ユーザーが新しい記事を作成できるようにする"
  end
end