---
id: ory-permission-language
title: Ory Permission Language specification
sidebar_label: Ory Permission Language
---

Enforcing fine-grained permissions is a critical building block of mature technology solutions that protect privacy and identity
in the information age. Several proprietary languages used to represent permission already exist, such as Rego or Casbin. Most
permissions are defined by developers who are likely familiar with Web technologies like JavaScript or Typescript. There is a need
for a developer-friendly configuration language for permissions that has a learning curve small enough so that most developers can
understand and use it with minimal effort. To fulfill this need, we defined the permissions configuration language as a subset of
the most common general-purpose programming language: JavaScript/TypeScript.

The Ory Permission Language is a syntactical subset of TypeScript. Along with type definitions for the syntax elements of the
language (such as `Namespace` or `Context`), users can get context help from their IDE while writing the configuration.

## Notation

The syntax is specified using the Extended Backus-Naur Form (EBNF):

```ebnf
Production  = production_name "=" [ Expression ] "." .
Expression  = Alternative { "|" Alternative } .
Alternative = Term { Term } .
Term        = production_name | token [ "…" token ] | Group | Option | Repetition .
Group       = "(" Expression ")" .
Option      = "[" Expression "]" .
Repetition  = "{" Expression "}" .
```

Productions are expressions constructed from terms and the following operators, in increasing precedence:

```ebnf
|   alternation
()  grouping
[]  option (0 or 1 times)
{}  repetition (0 to n times)
```

Lowercase production names are used to identify lexical tokens. Non-terminals are in CamelCase. Lexical tokens are enclosed in
double quotes `""` or single quotes `''`.

The form `a … b` represents the set of characters from a through b as alternatives. The horizontal ellipsis `…` is also used
elsewhere in the spec to informally denote various enumerations or code snippets that are not further specified.

## Configuration text representation

The configuration is encoded in UTF-8.

## Lexical elements

### Comments

1. Line comments start with the character sequence `//` and stop at the end of the line.
2. General comments start with the character sequence `/*` and stop with the first subsequent character sequence `*/`.
3. Documentation comments start with the character sequence `/**` and stop with the first subsequent character sequence `*/`.

### Identifiers

Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The
first character in an identifier must be a letter.

```ebnf
identifier = letter { letter | digit } .
digit      = "0" … "9" .
letter     = "A" … "Z" | "a" … "z" | "_" .
```

### String literals

String literals represent string constants as sequences of characters.

```ebnf
string_lit    = single_quoted | double_quoted .
single_quoted = "'" identifier "'" .
double_quoted = '"' identifier '"' .
```

### Keywords

The configuration language has the following keywords:

- `class`
- `implements`
- `related`
- `permits`
- `this`
- `ctx`
- `id`
- `imports`
- `exports`
- `as`

### Builtin Types

The following types are built in:

- `Context`
- `Namespace`
- `Namespace[]`
- `boolean`
- `string`
- `SubjectSet<T, R>`

In TypeScript, they would be defined as follows:

```typescript
type Context = { subject: never }

interface Namespace {
  related?: { [relation: string]: Namespace[] }
  permits?: { [method: string]: (ctx: Context) => boolean }
}

interface Array<Namespace> {
  includes(element: Namespace): boolean
  traverse(iteratorfn: (element: Namespace) => boolean): boolean
}

type SubjectSet<A extends Namespace, R extends keyof A["related"]> = A["related"][R] extends Array<infer T> ? T : never
```

### Operators

The following character sequences represent boolean operators:

| Operator | Signature      | Semantic                             |
| -------- | -------------- | ------------------------------------ |
| `&&`     | _x_ `&&` _y_   | true iff. both _x_ and _y_ are true  |
| `\|\|`   | _x_ `\|\|` _y_ | true iff. either _x_ or _y_ are true |
| `!`      | `!` _x_        | true iff. _x_ is false               |

The following character sequences represent miscellaneous operators:

| Operator | Example                                  | Semantic                                                          |
| -------- | ---------------------------------------- | ----------------------------------------------------------------- |
| `(`, `)` | `x()`                                    | call function `x`                                                 |
| `[]`     | `T[]`                                    | `T` is an array type                                              |
| `<`, `>` | `SubjectSet<Group, "members">`           | Type reference to the "members" relation of the "Group" namespace |
| `{`, `}` | `class x {}`                             | Scope delimiters                                                  |
| `=>`     | `list.transitive(el => f(el))`           | Lambda definition token.                                          |
| `.`      | `this.x`                                 | Property traversal token                                          |
| `:`      | `relation: type`                         | Relation/type separator token                                     |
| `=`      | `permits = {...}`                        | Assignment token                                                  |
| `,`      | `{x: 1, y: 2}`                           | Property separator                                                |
| `'`      | `'string'`                               | Single quoted string literal                                      |
| `"`      | `"string"`                               | Double quoted string literal                                      |
| `*`      | `import * from @ory/permission-language` | Import glob                                                       |
| `\|`     | `(User \| Group)[]`                      | Type union                                                        |

## Statements

### Type declaration

The top level of the configuration consists of a list of `class` declarations for each namespace. Each `class` consists of
relation declarations and permission declarations.

```ebnf
Config          = [ ClassDecl ] .
ClassDecl       = "class" identifier "implements" "Namespace" "{" ClassSpec "}" .
ClassSpec       = [ RelationDecls ] | [ PermissionDefns] .
```

The following example declares the type `User`.

```ts
class User implements Namespace {}
```

### Relation declaration

The `related` section of type declarations defines relations. Unlike regular TypeScript, `RelationName` must be a unique
identifier used as the relation strings in the tuples. The `TypeName` must be the name of another `type` that is defined above or
below (in TypeScript: a class that implements `Namespace`).

Type unions (`|`) can be used to denote that a relation can have subjects of multiple types, e.g.,
`viewers: (User | SubjectSet<Group, "members">)[]`, meaning that the subject of the "viewer" relation can be either a "User", or a
subject set "Group#members".

```ebnf
RelationDecls   = "related" "=" "{" { RelationName ":" ArrayType } "}" .
RelationName    = identifier .
ArrayType       = RelationType | ( "(" RelationType { "|" RelationType } ")" ) "[]" .
RelationType    = SubjectType | SubjectSetType .
SubjectType     = TypeName .
SubjectSetType  = "SubjectSet" "<" TypeName, string_lit ">" .
TypeName        = identifier .
```

Note that all relations are defined as array types `T[]` because there are naturally only many-to-many relations in Keto.

The following declares a type `Document` with three relations: `owners` and `viewers`, both of which have `users` as subjects.
Additionally, the relation `parent` has type `Document`.

```ts
class User {}

class Document {
  related = {
    parents: Document[]
    owners: User[]
    viewers: User[]
  }
}
```

### Permission definition

Permissions are defined as functions within class declarations that take a parameter `ctx` of type `Context` and evaluate to a
boolean `true` or `false`.

The type annotations for `ctx` and the return value are optional.

```ebnf
PermissionDefns = "permits" "=" "{" Permission [ "," Permission ] "}" .
Permission      = PermissionSign "=>" PermissionBody .
PermissionSign  = PermissionName ":" "(" "ctx" [ ":" "Context" ] ")" [ ":" "boolean" ] .
PermissionName  = identifier .
```

The `ctx` object is a fixed parameter that contains the `subject` for which the permission check should be conducted:

```ts
ctx = { subject: "some_user_id" }
```

```ebnf
PermissionBody  = ( "(" PermissionBody ")" ) | ( "!" PermissionBody ) | ( PermissionCheck | { Operator PermissionBody } ) .
Operator        = "||" | "&&" .
PermissionCheck = TransitiveCheck | IncludesCheck .
```

The body of a permission check is either one of:

- a `IncludesCheck`, a check that something is in a set, e.g., `this.related.viewers.includes(ctx.subject)`:

  ```ebnf
  IncludesCheck = Var "." "related" "." RelationName "." "includes" "(" "ctx" "." "subject" ")" .
  Var           = identifier .
  ```

- a `TranstitiveCheck`, a call to a permission on a relation, e.g., `this.related.parents.transitive(p => p.permits.view(ctx))`:

  ```ebnf
  TransitiveCheck = "this" "." "related" "." RelationName "." "transitive" "(" Var "=>" ( PermissionCall | IncludesCheck ) ")" .
  PermissionCall  = Var "." "permits" "." PermissionName "(" "ctx" ")" .
  ```

## Implementation notes

`IncludeCheck` and `TransitiveCheck` translate to Zanzibar concepts as follows:

| Keto Config                                               | Zanzibar AST                                                                         |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `this.related.R.includes(ctx.subject)`                    | `computed_userset { relation: "R" } }`                                               |
| `this.related.R.transitive(x = x.permits.P(ctx.subject))` | `tuple_to_userset { tupleset { relation: "R" } computed_userset { relation: "P" } }` |

## Type checking

The following type checks are performed once the config is fully parsed:

- Given a `TypeName` as `X` (e.g., in `RelationDecls`), we check that there exists a class declaration for `X`.
- Given a `SubjectSetType` as `SubjectSet<T, R>`, we check that `R` is a relation defined for `T`.
- Given an `IncludesCheck` as `this.related.R.includes(ctx.subject)`, we check that
  - `R` is a relation defined for the current namespace.
- Given a `TransitiveCheck` as `this.related.R.transitive(x = x.permits.P(ctx.subject))`, we check that
  - `R` is a relation defined for the current namespace and that
  - `P` is a permission defined for all types referenced by `R`.
- Given a `TransitiveCheck` as `this.related.R.transitive(x = x.related.S.includes(ctx.subject))`, we check that
  - `R` is a relation defined for the current namespace and that
  - `S` is a relation defined for all types referenced by `R`.

## Examples

The config can be type-checked in `strict` mode by TypeScript with the [noLib](https://www.typescriptlang.org/tsconfig#noLib)
option (preventing the standard globals), and the
[strictPropertyInitialization](https://www.typescriptlang.org/tsconfig#strictPropertyInitialization) option (allowing
uninitialized properties).

```ts
class User implements Namespace {
  related: {
    manager: User[]
  }
}

class Group implements Namespace {
  related: {
    members: (User | Group)[]
  }
}

class Folder implements Namespace {
  related: {
    parents: File[]
    viewers: (User | SubjectSet<Group, "members">)[]
  }

  permits = {
    view: (ctx: Context): boolean => this.related.viewers.includes(ctx.subject),
  }
}

class File implements Namespace {
  related: {
    parents: (File | Folder)[]
    viewers: (User | SubjectSet<Group, "members">)[]
    owners: (User | SubjectSet<Group, "members">)[]
    siblings: File[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.parents.traverse((p) => p.related.viewers.includes(ctx.subject)) ||
      this.related.parents.traverse((p) => p.permits.view(ctx)) ||
      this.related.viewers.includes(ctx.subject) ||
      this.related.owners.includes(ctx.subject),

    edit: (ctx: Context) => this.related.owners.includes(ctx.subject),

    rename: (ctx: Context) => this.related.siblings.traverse((s) => s.permits.edit(ctx)),
  }
}
```
