Packages
ash
3.27.7
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/authorizer.ex
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Authorizer do
@moduledoc """
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]`
"""
require Ash.BehaviourHelpers
@type state :: map
@type context :: map
@callback initial_state(
Ash.Resource.t(),
Ash.Resource.Record.t(),
Ash.Resource.Actions.action(),
Ash.Domain.t()
) :: state
@callback strict_check_context(state) :: [atom]
@callback strict_check(state, context) ::
{:authorized, state}
| {:continue, state}
| {:filter, Keyword.t()}
| {:filter, Keyword.t(), state}
| {:filter_and_continue, Keyword.t(), state}
| {:error, term}
@callback alter_filter(filter :: Ash.Filter.t(), state, context) ::
{:ok, Ash.Filter.t()} | {:error, Ash.Error.t()}
@callback add_calculations(Ash.Query.t() | Ash.Changeset.t(), state, context) ::
{:ok, Ash.Query.t() | Ash.Changeset.t(), state} | {:error, Ash.Error.t()}
@callback alter_results(state, list(Ash.Resource.Record.t()), context) ::
{:ok, list(Ash.Resource.Record.t())} | {:error, Ash.Error.t()}
@doc """
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.
"""
@callback apply_field_level_auth(
resource :: Ash.Resource.t(),
records :: list(Ash.Resource.Record.t()),
opts :: Keyword.t()
) :: {:ok, list(Ash.Resource.Record.t())} | {:error, Ash.Error.t()}
@callback check_context(state) :: [atom]
@callback check(state, context) ::
:authorized
| {:data, list(Ash.Resource.Record.t())}
| {:error, :forbidden, state}
| {:error, Ash.Error.t()}
@doc """
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.
"""
@callback protected_fields(Ash.Resource.t()) :: [atom()]
@callback exception(atom, state) :: Exception.t()
@optional_callbacks [
exception: 2,
add_calculations: 3,
alter_results: 3,
alter_filter: 3,
apply_field_level_auth: 3,
protected_fields: 1
]
defmacro __using__(_) do
quote do
@behaviour Ash.Authorizer
end
end
@doc false
@spec initial_state(
module(),
Ash.Resource.Record.t(),
Ash.Resource.t(),
Ash.Resource.Actions.action(),
Ash.Domain.t()
) :: state
def initial_state(module, actor, resource, action, domain) do
result = apply(module, :initial_state, [actor, resource, action, domain])
if is_map(result) do
result
else
raise Ash.Error.Framework.InvalidReturnType,
message: """
Invalid value returned from #{inspect(module)}.initial_state/4.
The callback #{inspect(__MODULE__)}.initial_state/4 expects a map (state).
"""
end
end
@doc false
@spec exception(module(), atom(), state()) :: Exception.t()
def exception(module, reason, state) do
if function_exported?(module, :exception, 2) do
apply(module, :exception, [reason, state])
else
if reason == :must_pass_strict_check do
Ash.Error.Forbidden.MustPassStrictCheck.exception([])
else
Ash.Error.Forbidden.exception([])
end
end
end
@doc false
@spec strict_check_context(module(), state()) :: [atom()]
def strict_check_context(module, state) do
result = apply(module, :strict_check_context, [state])
if is_list(result) and Enum.all?(result, &is_atom/1) do
Enum.uniq(result ++ [:query, :changeset])
else
raise Ash.Error.Framework.InvalidReturnType,
message: """
Invalid value returned from #{inspect(module)}.strict_check_context/1.
The callback #{inspect(__MODULE__)}.strict_check_context/1 expects a list of atoms.
"""
end
end
@doc false
@spec strict_check(module(), state(), context()) ::
{:authorized, state()}
| {:continue, state()}
| {:filter, Keyword.t()}
| {:filter, Keyword.t(), state()}
| {:filter_and_continue, Keyword.t(), state()}
| {:error, term()}
def strict_check(module, state, context) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:strict_check,
[state, context],
[
{:authorized, :_},
{:continue, :_},
{:filter, :_},
{:filter, :_, :_},
{:filter_and_continue, :_, :_},
{:error, :_}
],
behaviour: __MODULE__,
callback_name: "strict_check/2"
)
end
@doc false
@spec add_calculations(module(), Ash.Query.t() | Ash.Changeset.t(), state(), context()) ::
{:ok, Ash.Query.t() | Ash.Changeset.t(), state()} | {:error, Ash.Error.t()}
def add_calculations(module, query_or_changeset, state, context) do
if function_exported?(module, :add_calculations, 3) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:add_calculations,
[query_or_changeset, state, context],
[{:ok, :_, :_}, {:error, :_}],
behaviour: __MODULE__,
callback_name: "add_calculations/3"
)
else
{:ok, query_or_changeset, state}
end
end
@doc false
@spec alter_results(module(), state(), [Ash.Resource.Record.t()], context()) ::
{:ok, [Ash.Resource.Record.t()]} | {:error, Ash.Error.t()}
def alter_results(module, state, records, context) do
if function_exported?(module, :alter_results, 3) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:alter_results,
[state, records, context],
[{:ok, :_}, {:error, :_}],
behaviour: __MODULE__,
callback_name: "alter_results/3"
)
else
{:ok, records}
end
end
@doc false
@spec alter_filter(module(), state(), Ash.Filter.t(), context()) ::
{:ok, Ash.Filter.t()} | {:error, Ash.Error.t()}
def alter_filter(module, state, filter, context) do
if function_exported?(module, :alter_filter, 3) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:alter_filter,
[filter, state, context],
[{:ok, :_}, {:error, :_}],
behaviour: __MODULE__,
callback_name: "alter_filter/3"
)
else
{:ok, filter}
end
end
@doc false
@spec alter_sort(module(), state(), term(), context()) ::
{:ok, term()} | {:error, Ash.Error.t()}
def alter_sort(module, state, sort, context) do
if function_exported?(module, :alter_sort, 3) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:alter_sort,
[sort, state, context],
[{:ok, :_}, {:error, :_}],
behaviour: __MODULE__,
callback_name: "alter_sort/3"
)
else
{:ok, sort}
end
end
@doc false
@spec check_context(module(), state()) :: [atom()]
def check_context(module, state) do
result = apply(module, :check_context, [state])
if is_list(result) and Enum.all?(result, &is_atom/1) do
result
else
raise Ash.Error.Framework.InvalidReturnType,
message: """
Invalid value returned from #{inspect(module)}.check_context/1.
The callback #{inspect(__MODULE__)}.check_context/1 expects a list of atoms.
"""
end
end
@doc false
@spec check(module(), state(), context()) ::
:authorized
| {:data, [Ash.Resource.Record.t()]}
| {:error, :forbidden, state()}
| {:error, Ash.Error.t()}
def check(module, state, context) do
Ash.BehaviourHelpers.call_and_validate_return(
module,
:check,
[state, context],
[
:authorized,
{:data, :_},
{:error, :forbidden, :_},
{:error, :_}
],
behaviour: __MODULE__,
callback_name: "check/2"
)
end
@doc false
@spec protected_fields(module(), Ash.Resource.t()) :: [atom()]
def protected_fields(module, resource) do
if function_exported?(module, :protected_fields, 1) do
result = apply(module, :protected_fields, [resource])
if is_list(result) and Enum.all?(result, &is_atom/1) do
Enum.uniq(result)
else
raise Ash.Error.Framework.InvalidReturnType,
message: """
Invalid value returned from #{inspect(module)}.protected_fields/1.
The callback #{inspect(__MODULE__)}.protected_fields/1 expects a list of atoms.
"""
end
else
[]
end
end
@doc """
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.
"""
@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()}
def apply_field_level_auth(resource, records, opts \\ []) do
{records, single?} =
case records do
list when is_list(list) -> {list, false}
record -> {[record], true}
end
resource
|> Ash.Resource.Info.authorizers()
|> Enum.reduce_while({:ok, records}, fn module, {:ok, records} ->
if function_exported?(module, :apply_field_level_auth, 3) do
case Ash.BehaviourHelpers.call_and_validate_return(
module,
:apply_field_level_auth,
[resource, records, opts],
[{:ok, :_}, {:error, :_}],
behaviour: __MODULE__,
callback_name: "apply_field_level_auth/3"
) do
{:ok, records} -> {:cont, {:ok, records}}
{:error, error} -> {:halt, {:error, error}}
end
else
{:cont, {:ok, records}}
end
end)
|> case do
{:ok, [single]} when single? -> {:ok, single}
{:ok, records} -> {:ok, records}
{:error, error} -> {:error, error}
end
end
end