README.md

# AuvalOffice

<!-- MDOC -->

This is a library to define an authorization policy.
It is modeled after the [authorize](https://github.com/jfrolich/authorize) package,
but I made my own for a simple reason:

- I wanted to exercise my Elixir skills and see what happened if I wrote my own

Compared to [authorize](https://github.com/jfrolich/authorize),
this library has a couple of additional features:

- Authorizations return a **justification**.

  The result of an `authorize` call is always a triple `{result, rule, parameters}`
  where `result` is `:ok` or `:error`,
  `rule` specifies the rule that made the decision, and
  `parameters` gives the input parameters for the decision and any additional values that the
  authorization rule chooses to include.
  The reason for this decision is to provide a self-contained context of the decision
  to enable historic auditing.

- Support authorization **context**.

  Authorization rules can make use of a context parameter, for any ambient information
  that is not part of the (Subject, Object, Action) triple to be authorized.

  Additionally, you can define **fetcher** functions to declare context values that
  can be fetched to implement your policy, for example group memberships of a user,
  if that information is not part of your user record.

  Fetcher functions will only be invoked if the context value they fetch is not yet
  part of the context.

## Installation

At the moment, this library is not available on Hex, so install it via a direct github reference:

```elixir
def deps do
  [
    # ...
    {:auval_office, github: "braunse/auval_office"}
  ]
```

## HOWTO

Authorization happens in a **policy module**, which contains all the rules for a specific policy,
and the associated fetchers:

```elixir
defmodule Example.Policy
  use AuvalOffice.Policy

  # To define a policy, list rules one after another.
  # Basic rule syntax is like this:
  rule :a_symbol_or_string_naming_the_rule, action, subject, object do
    # Allow the access, and stop evaluating further rules
    :ok

    # As above, but provide additional decision context (e.g. for building an audit trail)
    {:ok, pigs: :learned_to_fly}

    # Disallow the access, and stop evaluating further rules
    :error

    # As above, but provide additional decision context (e.g. for building an audit trail)
    {:error, moon: :was_not_in_the_seventh_house}

    # Make no decision, and evaluate further rules
    :next
  end

  # Subject and object can be patterns, and bound names are available in the body of the rule
  rule :author_can_edit_blog_post, :edit, %User{id: id}, %Post{author_id: author_id} do
    if author_id == id do
      :ok
    else
      :next
    end
  end

  # Rules can be guarded by a condition.
  # The rule will be skipped unless the condition is true
  rule :author_can_edit_blog_post_shorter_definition, :edit %User{id: id}, %Post{author_id: author_id},
    when: id == author_id,
    do: :ok

  # Rules can refer to context by pattern matching, for example when additional information
  # has to be fetched in the course of evaluation
  rule :author_can_delete_blog_post_while_it_has_no_comments, :delete, %User{id: id}, %Post{author_id},
    context: %{post_comments: 0} do
    if author_id == id, do: :ok, else: :next
  end

  # To fetch the number of blog posts, use a fetcher.
  # You can match on subject, object, action and context.
  # As with rules, a guard clause can be given; the fetcher is skipped when the guard clause evaluates to false
  # Basic syntax:
  fetch :fetcher_id, :field_name, [parameter: pattern] do
    {:ok, value}
    # or {:error, error} to fail evaluation
  end
  
  # For example to fetch the number of blog post comments:
  fetch :blog_post_comment_count, :post_comments, object: %Post{id: id} do
    {:ok, BlogPost.count_comments()}
  end
end
```