Issho.Policy behaviour (Issho v0.4.0)

Copy Markdown View Source

Your app's answer to "may this actor do this here?", and to "what is this document?". Issho has no roles and no user model: it asks, and your app answers with whatever it already uses.

defmodule MyApp.Collaboration do
  @behaviour Issho.Policy

  @impl true
  def authorize(%Issho.Identity{} = who, "board:" <> id, action) do
    if MyApp.Boards.allowed?(who.claims["user_id"], id, action),
      do: :ok,
      else: {:error, :forbidden}
  end

  @impl true
  def document(_who, "board:" <> id) do
    board = MyApp.Boards.get!(id)
    {:ok, %{engine: Issho.Engine.Map, initial: board.content}}
  end
end

Actions

authorize/3 gets the room (the topic, such as "board:42") and one of:

  • :join: to enter the room, see its people and their places;
  • :read: to receive its documents and their changes, asked again for the changes others make, so that a person whose access you take away stops receiving them;
  • :write: to change its documents;
  • :comment: to change its comments (Issho.Comments): start a thread, reply, react, resolve. A reader (a grant with can: :read) may comment when you allow it, so a commenter is between a reader and a writer. A policy that answers {:error, _} for actions it does not know lets no one comment until it says :comment;
  • :lock: to hold an advisory lock on a place;
  • :interrupt: to pause, resume or stop an agent at work in the room (Issho.Work) that acts for someone else; the person an agent acts for may always;
  • :read_stream: to see a stream of the room that is for whomever the policy says (for: :policy, Issho.Streams); a stream for a person or for the room asks :read only.

A component that keeps its data outside Issho's documents (a grid of your database, which writes it itself) asks too, with Issho.Participant.authorize/2.

Issho asks on join and on every message in both directions, so keep it fast: put what it needs in the identity's claims when you sign the token (Issho.Token.sign/4), or cache.

Documents

document/2 says what a document is when it opens for the first time on a node: its engine (Issho.Engine), its initial content from your server (a browser never seeds a document), and the engine's options. What it says is the document, whoever opens it first: a page, the same document embedded in another page, an agent with no browser at all. A component's own spec (Issho.Live.open/3's :spec) is used only for a document document/2 does not know (it answers {:error, _}), so define here a document that opens in more than one place. A room's documents are the room itself ("board:42") and paths under it ("board:42/n7/body"), so the nested documents of a canvas share the room's people and authorization.

Summary

Types

What a document is: its engine, its initial content (given to the engine's Issho.Engine.init/2 when the store has nothing), and the engine's options.

Callbacks

Whether the actor may do the action in the room.

What a document is, when it opens for the first time on a node.

Functions

The room of a document: the part of its id before the first "/", or the whole id. The documents of a page share a room ("notebook:7/cell-1", "notebook:7/cell-2"), and with it its people and their places.

Types

action()

@type action() ::
  :join | :read | :write | :comment | :lock | :interrupt | :read_stream

document()

@type document() :: %{
  :engine => module(),
  optional(:initial) => term(),
  optional(:opts) => keyword()
}

What a document is: its engine, its initial content (given to the engine's Issho.Engine.init/2 when the store has nothing), and the engine's options.

Callbacks

authorize(t, room, action)

@callback authorize(Issho.Identity.t(), room :: String.t(), action()) ::
  :ok | {:error, term()}

Whether the actor may do the action in the room.

document(arg1, document_id)

(optional)
@callback document(Issho.Identity.t() | nil, document_id :: String.t()) ::
  {:ok, document()} | {:error, term()}

What a document is, when it opens for the first time on a node.

Functions

room(document_id)

@spec room(String.t()) :: String.t()

The room of a document: the part of its id before the first "/", or the whole id. The documents of a page share a room ("notebook:7/cell-1", "notebook:7/cell-2"), and with it its people and their places.

iex> Issho.Policy.room("notebook:7/cell-1")
"notebook:7"
iex> Issho.Policy.room("post:42")
"post:42"