README.md

# Dictator

Dictator is a plug-based authorization mechanism.

## Installation

First, you need to add `:dictator` to your list of dependencies on your `mix.exs`:

```elixir
def deps do
  [{:dictator, github: "subvisual/dictator"}]
end
```

## Usage

**IMPORTANT: `Dictator` assumes you have your current user in your
`conn.assigns`. See our [demo app](https://github.com/subvisual/dictator_demo)
for an example on integrating with guardian.**

To authorize your users, just add in your controller:

```elixir
defmodule ClientWeb.ThingController do
  use ClientWeb, :controller

  plug Dictator.Plug.Authorize

  # ...
end
```

That plug will automatically look for a `ClientWeb.Policies.Thing` module, which
should `use Dictator.Policy`, provide the resource that is being authorized
access to, and you can define two functions: `can?/3` and `load_resource/1`.

In `lib/client_web/policies/thing.ex`:

```elixir
defmodule ClientWeb.Policies.Thing do
  alias Client.Context.Thing

  use Dictator.Policy, for: Thing

  # User can edit, update, delete and show their own things
  def can?(%User{id: user_id}, action, %Thing{user_id: user_id})
    when action in [:edit, :update, :delete, :show], do: true

  # Any user can index, new and create things
  def can?(_, action, _) when action in [:index, :new, :create], do: true

  # Users can't do anything else (users editing, updating, deleting and showing)
  # on things they don't own
  def can?(_, _, _), do: false
end
```

This exact scenario is, in fact, so common that already comes bundled as
`Dictator.Policies.Standard`. This is equivalent to the previous definition:

```elixir
defmodule ClientWeb.Policies.Thing do
  alias Client.Context.Thing

  use Dictator.Policies.Standard, for: Thing
end
```

### Custom Options

The following params can be passed to `Dictator.Policy` and
`Dictator.Policies.Standard`:

- **`for:` (required)** - the module of the resource being accessed.
- **`repo:` (optional, automatically inferred)** - repo to load the resource.
- **`key:` (optional, default: `:id`)** - primary key of the resource being
  accessed.

The following params can be passed to `Dictator.Policies.Standard`:

- **`foreign_key:` (optional, `default: :user_id`)** - foreign key of the
  resource being accessed. If you're accessing a `Post` with that has an
  `organization_key` instead of a `user_id`, use this option to override it.
- **`owner_key:` (optional, default: `:id`)** - primary key of the owner of the
  resource. If you're accessing a `Post` with that belongs to an `Organization`
  that uses a `:slug` key as primary instead of an `id`, use this option.

The following params can be passed to `Dictator.Plug.Authorize`:

- **`only:` (optional, defaults to all actions)** - actions subject to
  authorization.
- **`policy:` (optional, infers the policy)** - policy to be used
- **`resource_key:` (optional, default: `:current_user`)** - key to use in the
  `conn.assigns` to load the currently logged in resource.

#### Overriding the Repo

By `use`ing `Dictator.Policy`, it will automatically infer the correct repo.  If
it cannot do so, it will fail compiling. If that happens, you need to pass the
correct repo module:

```elixir
defmodule ClientWeb.Policies.Thing do
  alias Client.Context.Thing
  alias Client.FunkyRepoForThings

  use Dictator.Policies.Standard, for: Thing, repo: FunkyRepoForThings
end
```

#### Using a different primary key

When getting the resource being accessed from the database, `Dictator` calls
`YourRepo.get_by(YourModule, id: id)`. But if you use a primary key other than
`id`, you can set it by overriding the `key` option:

```elixir
defmodule ClientWeb.Policies.Thing do
  alias Client.Context.Thing

  use Dictator.Policies.Standard, for: Thing, key: :uuid
end
```

If you need further customizing how the resource is loaded from the database,
you can override the `load_resource/1`, which receives the route params as
argument.

```elixir
defmodule ClientWeb.Policies.Thing do
  alias Client.Context.Thing
  alias Client.FunkyRepoForThings

  use Dictator.Policies.Standard, for: Thing

  def load_resource(params) do
    if params["uuid"] && params["name"] do
      FunkyRepoForThings.get_by(Thing, uuid: params["uuid"], name: [params["name"]])
    else
      FunkyRepoForThings.get(Thing, params["id"])
    end
  end
end
```

#### Overriding the resource foreign key

Sometimes the resource you're trying to access (e.g a `Post`) has a different
foreign key than `user_id`. Let's assume it belongs to an organization and has
an `organization_id` foreign key. You can override this by doing

```elixir
defmodule ClientWeb.Policies.Post do
  alias Client.Context.Post

  use Dictator.Policies.Standard, for: Post, foreign_key: :organization_id
```

#### Overriding the primary key of the resource being authorized

This option allows you to override the primary key of the resource that is being
granted or denied access. Imagine your User has a `uuid` primary key instead of
an `id`. You can use the standard police by doing the following:

```elixir
defmodule ClientWeb.Policies.Post do
  alias Client.Context.Post

  use Dictator.Policies.Standard, for: Post, owner_key: :id
```

#### Limitting the actions to be authorized

If you want to only limit authorization to a few actions you can use the `:only`
option when calling the plug. In your controller.

```elixir
defmodule ClientWeb.ThingController do
  use ClientWeb, :controller

  plug Dictator.Plug.Authorize, only: [:edit, :update]

  # ...
end
```

This way, all other actions will not go through the authorization plug and the
policy will only be enforced for the `edit` and `update` actions.


#### Overriding the policy to be used

By default, the `Authorize` plug will automatically infer the policy to be used.
`MyWebApp.UserController` would mean a `MyWebApp.Policies.User` policy to use.

However, by using the `:policy` option, that can be overriden

```elixir
defmodule ClientWeb.ThingController do
  use ClientWeb, :controller

  plug Dictator.Plug.Authorize, policy: MyPolicy

  # ...
end
```

#### Overriding the current user key

By default, the `Authorize` plug will automatically search for a `current_user`
in the `conn.assigns`. You can change this behaviour by using the `resource_key`
option in the `plug` call.

```elixir
defmodule ClientWeb.ThingController do
  use ClientWeb, :controller

  plug Dictator.Plug.Authorize, resource_key: :current_organization

  # ...
end
```

# Contributing

Feel free to contribute.

If you found a bug, open an issue. You can also open a PR for bugs or new
features. Your PRs will be reviewed and subject to our style guide and linters.

All contributions **must** follow the [Code of
Conduct](https://github.com/subvisual/dictator/blob/master/CODE_OF_CONDUCT.md)
and [Subvisual's guides](https://github.com/subvisual/guides).

## Setup

To clone and setup the repo:

```bash
git clone git@github.com:subvisual/dictator.git
cd dictator
bin/setup
```

And everything should automatically be installed for you.

To run the development server:

```bash
bin/server
```



# About

`Dictator` is maintained by [Subvisual](http://subvisual.com).

[<img alt="Subvisual logo" src="https://raw.githubusercontent.com/subvisual/guides/master/github/templates/subvisual_logo_with_name.png" width="350px" />](https://subvisual.com)