# `Sekisho.Policy`
[🔗](https://github.com/hattoriai/sekisho/blob/v0.1.0-dev/lib/sekisho/policy.ex#L1)

The rules of one resource: an Ecto schema.

Conditions are plain functions. `allow/2` and `deny/2` take the actor and a
permission and return `true`, `false` or a condition on the record, an
`Ecto.Query.dynamic/2` whose first binding is the resource:

    defmodule MyApp.Blog.PostPolicy do
      use Sekisho.Policy, for: MyApp.Blog.Post
      import Ecto.Query

      permissions [:read, :create, :update, :delete]

      @impl true
      def allow(%User{admin: true}, _permission), do: true
      def allow(%User{id: id}, :read), do: dynamic([p], p.published or p.author_id == ^id)
      def allow(%User{id: id}, permission) when permission in [:update, :delete],
        do: dynamic([p], p.author_id == ^id)
      def allow(%User{}, :create), do: true
      def allow(nil, :read), do: dynamic([p], p.published)

      @impl true
      def deny(_actor, :update), do: dynamic([p], p.locked)
    end

A permission no clause matches is not allowed: there is no need for a last
clause that returns `false`. `deny/2` wins over everything else.

The same condition answers a check (`allowed?/3`, on the record in the
database) and a list (`where_allowed/3`, in SQL), so they never disagree.
A condition about the actor alone (`true`, `false`) needs no query.

## Roles

Roles are the only part that is declared, because Sekisho checks them:

    permissions [:read, :comment, :update, :share, :delete]

    role :viewer, permissions: [:read]
    role :commenter, permissions: [:comment]
    role :editor, permissions: [:update]
    role :owner, permissions: [:share, :delete]

    derive :commenter, from: :editor
    derive :viewer, from: :commenter
    derive :editor, from: :owner
    derive :editor, from: {:org, :admin}
    derive :viewer, from: {:folder, :viewer}

    grant_to [:user, :group, :link], link_up_to: :viewer

  * `role` names a set of permissions.
  * `derive` gives a role from another: one of the same resource
    (`from: :editor`: editors are commenters), or one held on a related
    resource (`from: {:org, :admin}`: an org's admins are editors of its
    documents). The relation is the schema's own `belongs_to`, or one
    declared with `relation/2` and written in your `Sekisho.Sources`. A
    relation to the same schema (a folder's parent) is followed to any
    depth, with a recursive query.
  * `derive` also gives a permission from one held on a related record:
    `derive :view, from: {:org, :"investigations.view"}` (whoever has
    `:"investigations.view"` on the org, through a role or a rule, may
    `:view` its investigations), or from a role there
    (`derive :delete, from: {:org, :admin}`).
  * `grant_to` says who may be granted a role on a record (`grant/4` of
    your permissions module): people (`:user`), groups (`:group`), and
    anyone with a link (`:link`, up to a role). `by:` names the permission
    it takes to grant (`:share` by default).

Roles held come from grants (Sekisho's table), from your own tables
(`c:Sekisho.Sources.roles/1`), or both.

## Roles each record defines

`custom_roles()` lets each record of the schema (an org) define roles of its
own, kept in `sekisho_roles`, each a name and some of the policy's
permissions:

    permissions [:"investigations.view", :"investigations.create", :"team.manage"]

    role :owner, permissions: :all
    custom_roles()

The roles of the policy (`:owner`) are the same in every org and cannot
be changed; the custom ones are made with `create_role/4` of your
permissions module and granted by name (`grant(scope, ana, "Support", on:
org)`). Other resources derive from the permissions they give:

    derive :view, from: {:org, :"investigations.view"}

## Fields

`allow_fields/2` says which of a record's fields the actor may see:
`:all` (the default), a list, or `{:except, fields}`. `redact/2` of your
permissions module sets the others to `nil`.

# `condition`

```elixir
@type condition() :: boolean() | Ecto.Query.DynamicExpr.t()
```

What `allow/2` and `deny/2` return.

# `allow`
*optional* 

```elixir
@callback allow(actor :: term(), permission :: atom()) :: condition()
```

Whether the actor has the permission: everywhere, nowhere, or on the records of a condition.

# `allow_fields`
*optional* 

```elixir
@callback allow_fields(actor :: term(), record :: struct()) ::
  :all | [atom()] | {:except, [atom()]}
```

The fields of a record the actor may see.

# `deny`
*optional* 

```elixir
@callback deny(actor :: term(), permission :: atom()) :: condition()
```

Where the actor does not have the permission, whatever else allows it.

# `custom_roles`
*macro* 

Each record of the schema defines roles of its own (see the module's documentation).

# `derive`
*macro* 

A role given by another: `from: :editor` (a role of the same record), or
`from: {relation, role}` (a role held on a related record).

# `grant_to`
*macro* 

Who may be granted a role on a record: `[:user, :group, :link]`.

# `permissions`
*macro* 

The permissions of the resource: every permission its rules and roles name.

# `relation`
*macro* 

A relation that is not a `belongs_to` of the schema, to the schema given:
its query is `c:Sekisho.Sources.relation/2`.

# `role`
*macro* 

A role and the permissions it gives (`:all` for every one).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
