improvement: add more authorizer state management

Added more opportunities for authorizers to pass back state.
This is being used to ensure that ash policy authorizer errors
can always have enough information to provide a policy breakdown
This commit is contained in:
Zach Daniel 2021-12-21 12:24:12 -05:00
parent 6b95dec339
commit ce3ae44a4c
3 changed files with 37 additions and 9 deletions

View file

@ -36,7 +36,11 @@ defmodule Ash.Authorizer do
if function_exported?(module, :exception, 2) do if function_exported?(module, :exception, 2) do
module.exception(reason, state) module.exception(reason, state)
else else
Ash.Error.Forbidden.exception([]) if reason == :must_pass_strict_check do
Ash.Error.Forbidden.MustPassStrictCheck.exception([])
else
Ash.Error.Forbidden.exception([])
end
end end
end end

View file

@ -38,7 +38,6 @@ defmodule Ash.Engine.Request do
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
alias Ash.Authorizer alias Ash.Authorizer
alias Ash.Error.Forbidden.MustPassStrictCheck
alias Ash.Error.Invalid.{DuplicatedPath, ImpossiblePath} alias Ash.Error.Invalid.{DuplicatedPath, ImpossiblePath}
require Ash.Query require Ash.Query
@ -504,6 +503,21 @@ defmodule Ash.Engine.Request do
:authorized -> :authorized ->
{:ok, set_authorizer_state(request, authorizer, :authorized), notifications, []} {:ok, set_authorizer_state(request, authorizer, :authorized), notifications, []}
{:filter, authorizer_state, filter} ->
request
|> set_authorizer_state(authorizer, authorizer_state)
|> apply_filter(authorizer, filter, true)
|> case do
{:ok, request} ->
{:ok, request, notifications, []}
{:ok, request, new_notifications, deps} ->
{:ok, request, new_notifications ++ notifications, deps}
other ->
other
end
{:filter, filter} -> {:filter, filter} ->
request request
|> apply_filter(authorizer, filter, true) |> apply_filter(authorizer, filter, true)
@ -518,8 +532,13 @@ defmodule Ash.Engine.Request do
other other
end end
{:filter_and_continue, _, _} when strict_check_only? -> {:filter_and_continue, _, authorizer_state} when strict_check_only? ->
{:error, MustPassStrictCheck.exception(resource: request.resource)} {:error,
Authorizer.exception(
authorizer,
:must_pass_strict_check,
authorizer_state
)}
{:filter_and_continue, filter, new_authorizer_state} -> {:filter_and_continue, filter, new_authorizer_state} ->
request request
@ -536,8 +555,13 @@ defmodule Ash.Engine.Request do
other other
end end
{:continue, _} when strict_check_only? -> {:continue, authorizer_state} when strict_check_only? ->
{:error, MustPassStrictCheck.exception(resource: request.resource)} {:error,
Authorizer.exception(
authorizer,
:must_pass_strict_check,
authorizer_state
)}
{:continue, authorizer_state} -> {:continue, authorizer_state} ->
{:ok, set_authorizer_state(request, authorizer, authorizer_state), notifications, []} {:ok, set_authorizer_state(request, authorizer, authorizer_state), notifications, []}

View file

@ -2,15 +2,15 @@ defmodule Ash.Error.Forbidden.MustPassStrictCheck do
@moduledoc "Used when unreachable code/conditions are reached in the framework" @moduledoc "Used when unreachable code/conditions are reached in the framework"
use Ash.Error.Exception use Ash.Error.Exception
def_ash_error([:resource], class: :forbidden) def_ash_error([], class: :forbidden)
defimpl Ash.ErrorKind do defimpl Ash.ErrorKind do
def id(_), do: Ash.UUID.generate() def id(_), do: Ash.UUID.generate()
def code(_), do: "must_pass_strict_check" def code(_), do: "must_pass_strict_check"
def message(%{resource: resource}) do def message(_) do
"A request against #{inspect(resource)} was required to pass strict check, but it did not" "The request was required to pass strict check, but it did not"
end end
end end
end end