README.md

# Bosun

A simple authorization package that uses protocols.

## Installation

If [available in Hex](https://hex.pm/packages/bosun), the package can be installed
by adding `bosun` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:bosun, "~> 1.0.1"}
  ]
end
```

## Basic Usage

To use Bosun all you need to do is define your policy by implementing the `Bosun.Policy` protocol for a struct. Example below:

```elixir
defmodule User do
  defstruct role: :guest, username: "", blocked: false
end

defmodule Post do
  defstruct title: "", body: ""
end

defimpl Bosun.Policy, for: Post do
  alias Bosun.Context

  def permitted?(_resource, _action, %User{role: :admin}, context, _options) do
    Context.permit(context, "Admins are allowed to do anything")
  end

  def permitted?(%Post{title: "A Guest Post"}, _action, %User{role: :guest}, context, _options) do
    Context.permit(context, "Guests are allowed to do stuff to guest posts")
  end

  def permitted?(_resource, :read, %User{role: :guest}, context, _options) do
    Context.permit(context, "Guests are allowed to read posts")
  end

  def permitted?(_resource, :comment, %User{role: :guest} = user, context, options) do
    if options[:super_fan] do
      Context.permit(context, "Super fans are permitted")
    else
      Context.deny(context, "Guests that are not super fans are not permitted")
    end
    |> blocked_commenter?(user)
  end

  def blocked_commenter?(%Context{permitted: true} = context, %User{blocked: true}) do
    Context.deny(context, "User blocked from commenting")
  end

  def blocked_commenter?(context, _user) do
    context
  end

  def permitted?(_resource, :update, %User{role: :guest}, context, _options) do
    Context.deny(context, "User is a guest")
  end

  def permitted?(_resource, _action, _user, context, _options) do
    context
  end
end
```

After defining your policy as seen above anywhere in your codebase you can call the `Bosun.permit?/3` or `Bosun.permit?/4` functions.

```elixir
if Bosun.permit?(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true) do
  do_something()
else
  Logger.error("Boom!!!?!")
end

case Bosun.permit(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true) do
  {:ok, _} -> do_something()
  {:error, context} -> Logger.error(context.reason)
end

try do
  Bosun.permit!(%User{role: :guest}, :comment, %Post{title: "Another Guest Post"}, super_fan: true)
  do_something()
rescue
   e in Impermissible -> Logger.error(e.message)
 end
```

You can define an `Any` implementation as a fallback policy

```elixir
defimpl Bosun.Policy, for: Any do
  alias Bosun.Context

  def authorized?(_resource, _action, _subject, context, _options) do
    Context.reject(context, "Impermissible")
  end
end
```

## Configuration

Here is the default library config.

```elixir
config :bosun,
  debug: true
```

## Todo

- [ ] improve documentation

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/bosun>.