OxL Reference

13.4 Authorisation and policies

oxl
policy Orders {
  allow get(id)        if caller.isUser(db.find(Order, id)?.userId)
  allow list, recent   if caller.has(role: .Support)
  allow *              if caller.service("billing")
  deny  delete         unless caller.has(role: .Admin) && caller is .User(mfa: true, ..)
}

@public rpc health() -> Ok
@allow(role: .Admin) rpc purge() -> Ok
@internal rpc create(…) -> Order
  • The subject of a policy: a service (rules per rpc), a sharded actor (per msg), a topic (publish, subscribe), the dynamic config (set).
  • Evaluation: if at least one deny matches — refusal; otherwise if at least one allow matches — permission; otherwise refusal. A refusal is AccessDenied (PERMISSION_DENIED).
  • A condition is an expression with access to the rpc parameters by name, caller, ctx; the effects pure and reading db are allowed. Exception: the conditions of dynamic-config policies (policy Limits { allow set if … }, §14.2) are pure only, without db and ctx (an edition 1.0 correction, D-8056; DIV-028; D-5203).
  • @allow(role: r) is shorthand for allow m if caller.has(role: r). The form @allow(lint: rule, reason: "…") is not an access policy but a suppression of an analyzer rule (III.4).
  • The condition of a topic publish rule sees caller, element (the published value) and, for sharded by, key; the condition of subscribe sees only caller. External principals are admitted if no deny condition holds and an allow condition does; a rule naming another operation is the error unknown-policy-target (D-8064; D-7790).
  • The access model (oxl access, the "Permissions" section of the inspector) covers rpcs, messages of sharded actors and topic operations: for each of them the rules and the roles that call it; an element without rules is marked open (D-8064; D-7790, D-7792).

Automatic permissions between services. The compiler builds a call graph: which code of which role calls which rpcs, messages and topics. For every pair "role → method" an implicit rule allow m if caller.service is role is generated. All other calls from services are forbidden at the level of the mTLS identity.

External principals. For User and Anonymous only the explicit applies: an allow rule, @public or @allow. Without them the call is rejected. @internal explicitly marks a method as inaccessible to external principals. An rpc that is not allowed to external principals, is not marked @internal and is not called from the program's code is the compile error missing-policy (the method is accessible to nobody).

Data permissions:

oxl
@table("orders") @owner(.userId, bypass: [.Support, .Admin])
data Order { … }

data Employee {
  name: str
  @visible(role: .HR) salary: Money?
}
  • @owner(.field) — queries to the table from a User without the roles from bypass automatically get the condition field == caller.id. Limits of action: the condition is added to find, query, count, update, delete; it is not applied to load(…) and to raw SQL (execute) — access through them MUST be restricted by a policy; writing a row whose owner differs from caller is rejected (an edition 1.0 correction, D-8056; DIV-029; D-5030, D-5069).
  • @visible(role: r) — the field is not passed to principals without the role r; the field MUST be optional or have a default value.
  • @audit on an rpc or a policy writes every decision to the system topic oxl.audit.
  • oxl access diff shows the change of permissions after a code change; oxl access who-can Orders.delete shows who can call the method and through which roles.

data-rights, missing-policy, unknown-policy-target

See also

Source: docs/spec/oxl-standard.en.md