Ash.Policy.Policy (ash v3.25.2)

Copy Markdown View Source

Represents a policy on an Ash.Resource

Summary

Functions

Evaluates a policy's check chain against already-computed facts and returns its decision.

Returns the first non-bypass policy considered responsible for a forbidden outcome, along with its computed state.

Default Options for Crux scenarios

Types

error_message()

@type error_message() ::
  String.t()
  | (subject(), error_message_context() -> String.t() | Exception.t())

error_message_context()

@type error_message_context() :: %{
  resource: Ash.Resource.t(),
  action: Ash.Resource.Actions.action() | nil,
  actor: Ash.actor() | nil,
  domain: Ash.Domain.t() | nil,
  tenant: Ash.ToTenant.t() | nil
}

subject()

@type subject() :: Ash.Query.t() | Ash.Changeset.t() | Ash.ActionInput.t()

t()

@type t() :: %Ash.Policy.Policy{
  __spark_metadata__: Spark.Dsl.Entity.spark_meta(),
  access_type: :strict | :filter | :runtime,
  bypass?: boolean(),
  condition: nil | Ash.Policy.Check.ref() | [Ash.Policy.Check.ref()],
  description: String.t() | nil,
  error_message: error_message() | nil,
  policies: [Ash.Policy.Check.t()]
}

Functions

evaluate(policy, facts)

@spec evaluate(t(), map()) :: :authorized | :forbidden | :unknown

Evaluates a policy's check chain against already-computed facts and returns its decision.

Walks checks in source order. The first decisive check fixes the decision:

  • authorize_if X with X true → :authorized
  • authorize_unless X with X false → :authorized
  • forbid_if X with X true → :forbidden
  • forbid_unless X with X false → :forbidden

Anything else leaves the state at :unknown and the walk continues. If no check is decisive, the policy ends at :unknown — callers that need a binary "did this policy deny?" should treat :unknown as :forbidden, matching Ash's "if nothing authorized, the request is forbidden" rule.

Reads from the supplied facts map only. Strict checks are not invoked, so this is safe to call from error-construction paths where rerunning a strict check would surface unrelated errors (e.g. missing calculation arguments). For pre-flight call sites that need lazy strict-check evaluation, use the private policy_fails_statically?/2 in the policy authorizer.

expression(policies, check_context)

@spec expression(
  policies ::
    t() | Ash.Policy.FieldPolicy.t() | [t() | Ash.Policy.FieldPolicy.t()],
  check_context :: Ash.Policy.Check.context()
) :: Crux.Expression.t(Ash.Policy.Check.ref())

fetch_fact(facts, check)

@spec fetch_fact(
  facts :: map(),
  check :: Ash.Policy.Check.t() | Ash.Policy.Check.ref()
) ::
  {:ok, Crux.Expression.t(Ash.Policy.Check.ref())} | :error

fetch_or_strict_check_fact(authorizer, check)

responsible_for_forbidden(policies, facts)

@spec responsible_for_forbidden([t() | Ash.Policy.FieldPolicy.t()], map()) ::
  {t(), :forbidden | :unknown} | nil

Returns the first non-bypass policy considered responsible for a forbidden outcome, along with its computed state.

A policy is considered responsible if:

  • Its condition applies given the facts (no condition resolves to {:ok, false}),
  • It is not a bypass (bypasses don't deny on their own; an unsatisfied bypass just falls through to the next policy), and
  • Its evaluate/2 decision is :forbidden (explicit deny) or :unknown (no check authorized).

Within those, explicit :forbidden is preferred over :unknown. Returns nil when no non-bypass policy is responsible.

scenario_options(check_context)

@spec scenario_options(check_context :: Ash.Policy.Check.context()) ::
  Crux.opts(Ash.Policy.Check.ref())

Default Options for Crux scenarios

solve(authorizer)

@spec solve(authorizer :: Ash.Policy.Authorizer.t()) ::
  {:ok, boolean() | [map()], Ash.Policy.Authorizer.t()}
  | {:error, Ash.Policy.Authorizer.t(), Ash.Error.t()}