Ash.Authorizer behaviour (ash v3.32.2)

Copy Markdown View Source

The interface for an ash authorizer

These will typically be implemented by an extension, but a custom one can be implemented by defining an extension that also adopts this behaviour.

Then you can extend a resource with authorizers: [YourAuthorizer]

Summary

Callbacks

Apply field-level authorization to a list of records that already have values populated in memory, without re-fetching them through a read.

Evaluate field-level authorization for the given fields without any records in hand, in the context of the given subject's action.

Returns the fields this authorizer may protect at field level for the resource.

Functions

Apply field-level authorization to records that are already in memory, scrubbing any fields the actor isn't allowed to see.

Evaluate field-level authorization for the given fields without any records in hand, in the context of the given subject's action.

Types

context()

@type context() :: map()

state()

@type state() :: map()

Callbacks

add_calculations(arg1, state, context)

(optional)
@callback add_calculations(Ash.Query.t() | Ash.Changeset.t(), state(), context()) ::
  {:ok, Ash.Query.t() | Ash.Changeset.t(), state()} | {:error, Ash.Error.t()}

alter_filter(filter, state, context)

(optional)
@callback alter_filter(filter :: Ash.Filter.t(), state(), context()) ::
  {:ok, Ash.Filter.t()} | {:error, Ash.Error.t()}

alter_results(state, list, context)

(optional)
@callback alter_results(state(), [Ash.Resource.Record.t()], context()) ::
  {:ok, [Ash.Resource.Record.t()]} | {:error, Ash.Error.t()}

apply_field_level_auth(resource, records, opts)

(optional)
@callback apply_field_level_auth(
  resource :: Ash.Resource.t(),
  records :: [Ash.Resource.Record.t()],
  opts :: Keyword.t()
) :: {:ok, [Ash.Resource.Record.t()]} | {:error, Ash.Error.t()}

Apply field-level authorization to a list of records that already have values populated in memory, without re-fetching them through a read.

Implementations should walk each record and substitute %Ash.ForbiddenField{} for any field the actor isn't allowed to see (typically via field policies).

This is the lightweight counterpart to add_calculations/3 + the read pipeline's field-policy scrubbing — used when you have records in hand and just need to enforce field-level visibility without running a load.

check(state, context)

@callback check(state(), context()) ::
  :authorized
  | {:data, [Ash.Resource.Record.t()]}
  | {:error, :forbidden, state()}
  | {:error, Ash.Error.t()}

check_context(state)

@callback check_context(state()) :: [atom()]

evaluate_field_policies(subject, fields, opts)

(optional)
@callback evaluate_field_policies(
  subject :: Ash.Query.t() | Ash.Changeset.t() | Ash.ActionInput.t(),
  fields :: [atom()],
  opts :: Keyword.t()
) ::
  {:ok, %{required(atom()) => boolean() | {:filter, term()}}}
  | {:error, Ash.Error.t()}

Evaluate field-level authorization for the given fields without any records in hand, in the context of the given subject's action.

Implementations should return a map with an entry for every requested field, where each value is:

  • true - the actor can see the field
  • false - the actor cannot see the field
  • {:filter, expr} - visibility depends on the record; the actor can see the field on records matching expr

exception(atom, state)

(optional)
@callback exception(atom(), state()) :: Exception.t()

initial_state(t, t, action, t)

protected_fields(t)

(optional)
@callback protected_fields(Ash.Resource.t()) :: [atom()]

Returns the fields this authorizer may protect at field level for the resource.

This is metadata for callers that need to know which fields can be hidden by authorization without running an action. Authorizers that do not implement this callback are treated as protecting no fields.

strict_check(state, context)

@callback strict_check(state(), context()) ::
  {:authorized, state()}
  | {:continue, state()}
  | {:filter, Keyword.t()}
  | {:filter, Keyword.t(), state()}
  | {:filter_and_continue, Keyword.t(), state()}
  | {:error, term()}

strict_check_context(state)

@callback strict_check_context(state()) :: [atom()]

Functions

apply_field_level_auth(resource, records, opts \\ [])

@spec apply_field_level_auth(
  Ash.Resource.t(),
  Ash.Resource.Record.t() | [Ash.Resource.Record.t()],
  Keyword.t()
) ::
  {:ok, Ash.Resource.Record.t() | [Ash.Resource.Record.t()]}
  | {:error, Ash.Error.t()}

Apply field-level authorization to records that are already in memory, scrubbing any fields the actor isn't allowed to see.

Walks each authorizer configured on the resource and invokes its apply_field_level_auth/3 callback if defined. Returns the records with forbidden fields replaced by %Ash.ForbiddenField{}.

Use this when you have records in hand (for example, returned from a generic action or constructed in memory) and want field policies applied without driving the records through a full read.

Supported options:

  • :actor - the actor whose visibility is being checked.
  • :tenant - the tenant the records belong to.
  • :domain - the domain context.

evaluate_field_policies(subject, fields, opts \\ [])

@spec evaluate_field_policies(
  Ash.Query.t() | Ash.Changeset.t() | Ash.ActionInput.t(),
  [atom()],
  Keyword.t()
) ::
  {:ok, %{required(atom()) => boolean() | {:filter, term()}}}
  | {:error, Ash.Error.t()}

Evaluate field-level authorization for the given fields without any records in hand, in the context of the given subject's action.

Walks each authorizer configured on the subject's resource and invokes its evaluate_field_policies/3 callback if defined, combining the results such that a field is only visible if every authorizer allows it. Authorizers that don't implement the callback place no restrictions on any field.

Returns a map with an entry for every requested field, where each value is:

  • true - the actor can see the field
  • false - the actor cannot see the field
  • {:filter, expr} - visibility depends on the record; the actor can see the field on records matching expr

You should typically use Ash.can_see_fields?/4 or Ash.can_see_fields/4 instead of calling this directly.

Supported options:

  • :actor - the actor whose visibility is being checked.
  • :tenant - the tenant to evaluate against.
  • :domain - the domain context.