fix: handle atomic_ref templates and changing_attributes/1

This commit is contained in:
Zach Daniel 2024-02-14 13:55:19 -05:00
parent e9d2d8c575
commit f23f0a29fe
5 changed files with 96 additions and 44 deletions

View file

@ -904,7 +904,7 @@ defmodule Ash.Api do
end end
defp alter_source({:ok, true, query}, api, actor, %Ash.Changeset{} = subject, opts) do defp alter_source({:ok, true, query}, api, actor, %Ash.Changeset{} = subject, opts) do
case alter_source({:ok, true}, api, actor, subject, opts) do case alter_source({:ok, true}, api, actor, subject, Keyword.put(opts, :base_query, query)) do
{:ok, true, new_subject} -> {:ok, true, new_subject, query} {:ok, true, new_subject} -> {:ok, true, new_subject, query}
other -> other other -> other
end end
@ -939,6 +939,45 @@ defmodule Ash.Api do
case subject do case subject do
%Ash.Query{} = query -> %Ash.Query{} = query ->
alter_query(query, authorizer, authorizer_state, context)
%Ash.Changeset{} = changeset ->
context = Map.put(context, :changeset, changeset)
with {:ok, changeset, authorizer_state} <-
Ash.Authorizer.add_calculations(
authorizer,
changeset,
authorizer_state,
context
) do
if opts[:base_query] do
case alter_query(opts[:base_query], authorizer, authorizer_state, context) do
{:ok, true, query} ->
{:ok, true, changeset, query}
other ->
other
end
else
{:ok, true, changeset}
end
end
%Ash.ActionInput{} = subject ->
{:ok, true, subject}
end
end
)
end
else
{:ok, true}
end
end
defp alter_source(other, _, _, _, _), do: other
defp alter_query(query, authorizer, authorizer_state, context) do
context = Map.put(context, :query, query) context = Map.put(context, :query, query)
with {:ok, query, _} <- with {:ok, query, _} <-
@ -969,33 +1008,8 @@ defmodule Ash.Api do
) do ) do
{:ok, true, %{query | filter: hydrated, sort: new_sort}} {:ok, true, %{query | filter: hydrated, sort: new_sort}}
end end
%Ash.Changeset{} = changeset ->
context = Map.put(context, :changeset, changeset)
with {:ok, changeset, _} <-
Ash.Authorizer.add_calculations(
authorizer,
changeset,
authorizer_state,
context
) do
{:ok, true, changeset}
end end
%Ash.ActionInput{} = subject ->
{:ok, true, subject}
end
end
)
end
else
{:ok, true}
end
end
defp alter_source(other, _, _, _, _), do: other
defp run_check(api, actor, subject, opts) do defp run_check(api, actor, subject, opts) do
authorizers = authorizers =
Ash.Resource.Info.authorizers(subject.resource) Ash.Resource.Info.authorizers(subject.resource)

View file

@ -601,6 +601,8 @@ defmodule Ash.Filter do
{:_atomic_ref, field} when is_atom(field) -> {:_atomic_ref, field} when is_atom(field) ->
if changeset do if changeset do
Ash.Changeset.atomic_ref(changeset, field) Ash.Changeset.atomic_ref(changeset, field)
else
{:_atomic_ref, field}
end end
{:_context, fields} when is_list(fields) -> {:_context, fields} when is_list(fields) ->

View file

@ -720,6 +720,15 @@ defmodule Ash.Filter.Runtime do
end) end)
end end
defp resolve_expr({:_actor, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_arg, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_ref, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_ref, _, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_parent, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_parent, _, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_atomic_ref, _}, _, _, _, _), do: :unknown
defp resolve_expr({:_context, _}, _, _, _, _), do: :unknown
defp resolve_expr(other, _, _, _, _), do: {:ok, other} defp resolve_expr(other, _, _, _, _), do: {:ok, other}
defp try_cast_arguments(:var_args, args) do defp try_cast_arguments(:var_args, args) do

View file

@ -1051,7 +1051,7 @@ defmodule Ash.Policy.Authorizer do
{{check_module, check_opts}, true} -> {{check_module, check_opts}, true} ->
result = result =
try do try do
check_module.auto_filter(authorizer.actor, authorizer, check_opts) nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts))
rescue rescue
e -> e ->
reraise Ash.Error.to_ash_error(e, __STACKTRACE__, reraise Ash.Error.to_ash_error(e, __STACKTRACE__,
@ -1071,9 +1071,16 @@ defmodule Ash.Policy.Authorizer do
result = result =
try do try do
if :erlang.function_exported(check_module, :auto_filter_not, 3) do if :erlang.function_exported(check_module, :auto_filter_not, 3) do
nil_to_false(
check_module.auto_filter_not(authorizer.actor, authorizer, check_opts) check_module.auto_filter_not(authorizer.actor, authorizer, check_opts)
)
else else
[not: check_module.auto_filter(authorizer.actor, authorizer, check_opts)] [
not:
nil_to_false(
check_module.auto_filter(authorizer.actor, authorizer, check_opts)
)
]
end end
rescue rescue
e -> e ->
@ -1103,6 +1110,9 @@ defmodule Ash.Policy.Authorizer do
end) end)
end end
defp nil_to_false(nil), do: false
defp nil_to_false(v), do: v
def print_tuple_boolean({op, l, r}) when op in [:and, :or] do def print_tuple_boolean({op, l, r}) when op in [:and, :or] do
"(#{print_tuple_boolean(l)} #{op} #{print_tuple_boolean(r)})" "(#{print_tuple_boolean(l)} #{op} #{print_tuple_boolean(r)})"
end end
@ -1148,12 +1158,15 @@ defmodule Ash.Policy.Authorizer do
{{check_module, check_opts}, required_status} -> {{check_module, check_opts}, required_status} ->
additional_filter = additional_filter =
if required_status do if required_status do
check_module.auto_filter(authorizer.actor, authorizer, check_opts) nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts))
else else
if :erlang.function_exported(check_module, :auto_filter_not, 3) do if :erlang.function_exported(check_module, :auto_filter_not, 3) do
check_module.auto_filter_not(authorizer.actor, authorizer, check_opts) nil_to_false(check_module.auto_filter_not(authorizer.actor, authorizer, check_opts))
else else
[not: check_module.auto_filter(authorizer.actor, authorizer, check_opts)] [
not:
nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts))
]
end end
end end

View file

@ -40,16 +40,30 @@ defmodule Ash.Policy.Check.ChangingAttributes do
{:cont, expr} {:cont, expr}
{{:ok, from}, {:ok, to}} -> {{:ok, from}, {:ok, to}} ->
if expr == true do
{:cont,
Ash.Expr.expr(not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to))}
else
{:cont,
Ash.Expr.expr( Ash.Expr.expr(
^expr and not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to) ^expr and not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to)
) )}
end
{{:ok, from}, :error} -> {{:ok, from}, :error} ->
if expr == true do
{:cont, Ash.Expr.expr(ref(attribute) != ^from)}
else
{:cont, Ash.Expr.expr(^expr and ref(attribute) != ^from)} {:cont, Ash.Expr.expr(^expr and ref(attribute) != ^from)}
end
{:error, {:ok, to}} -> {:error, {:ok, to}} ->
if expr == true do
{:cont, Ash.Expr.expr(^atomic_ref(attribute) != ^to)}
else
{:cont, Ash.Expr.expr(^expr and ^atomic_ref(attribute) != ^to)} {:cont, Ash.Expr.expr(^expr and ^atomic_ref(attribute) != ^to)}
end end
end
else else
{:cont, expr} {:cont, expr}
end end