Setup

Migration

mix licet.gen.migration
mix ecto.migrate

Supervision tree

{Licet,
 name: MyApp.Licet,
 repo: MyApp.Repo,
 schema: {:file, Application.app_dir(:my_app, "priv/licet/schema.zed")}}

Schema

Definitions

definition user {}

definition team {
  relation member: user
}

definition document {
  relation parent: folder
  relation owner: user
  relation viewer: user | user:* | team#member
  permission edit = owner + parent->edit
  permission view = viewer + edit
}

Operators

SyntaxMeaning
a + beither grants
a & bboth grant
a - ba grants and b doesn't
rel->permperm on any object in rel
rel.any(perm)same as rel->perm
rel.all(perm)perm on every object in rel
nilnothing
user:*every user
team#membermembers of a team

Precedence, tightest first: arrows, +, &, -.

Expiration

use expiration

definition document {
  relation viewer: user with expiration
}

Relationships

Text form

TextMeaning
document:1#owner@user:adaAda owns document 1
document:1#viewer@team:eng#memberteam eng's members view it
document:1#viewer@user:*every user views it
document:1#viewer@user:bob[expiration:2027-01-01T00:00:00Z]until 2027

Writes

{:ok, token} =
  Licet.write(MyApp.Licet, [
    {:create, "document:1#owner@user:ada"},
    {:touch, "document:1#viewer@user:bob"},
    {:delete, "document:1#viewer@user:cara"}
  ])

:create fails if the relationship exists. :touch creates or updates it. :delete removes it.

Delete by filter

Licet.delete(MyApp.Licet, %{resource_type: "document", resource_id: "1"})

Read

{:ok, relationships} =
  Licet.read(MyApp.Licet, %{resource_type: "document", resource_id: "1"})

Queries

Check

Licet.check(MyApp.Licet, "document:1", "view", "user:ada")
#=> {:ok, :allowed}

Bulk check

Licet.check_bulk(MyApp.Licet, [
  %{resource: "document:1", permission: "view", subject: "user:ada"},
  %{resource: "document:2", permission: "view", subject: "user:ada"}
])
#=> {:ok, [{:ok, :allowed}, {:ok, :denied}]}

Lookup resources

{:ok, ids, cursor} =
  Licet.lookup_resources(MyApp.Licet, "document", "view", "user:ada", limit: 100)

Lookup subjects

{:ok, subjects, cursor} =
  Licet.lookup_subjects(MyApp.Licet, "document:1", "view", "user")
#=> [%{subject: "user:*", excluded: ["user:dan"]}, ...]

Explain

{:ok, :allowed, trace} = Licet.explain(MyApp.Licet, "document:1", "view", "user:ada")

Consistency

Modes

OptionSees
:minimize_latencya shared snapshot, cached (default)
{:at_least, token}at least the write behind token
{:exact, token}exactly the snapshot of token
:fullthe latest committed data, uncached

Usage

Licet.check(MyApp.Licet, "document:1", "view", "user:ada",
  consistency: {:at_least, token}
)

Errors

Shape

{:error, %Licet.Error{reason: :unknown_relation, message: message}}

Common reasons

ReasonCause
:invalid_relationshipmalformed tuple text
:unknown_typetype not in the schema
:unknown_relationrelation or permission not in the schema
:already_exists:create of an existing tuple
:timeoutthe check took longer than :timeout
:unsafe_schema_changestored tuples would be stranded

See Licet.Error for the full list.