diff --git a/lib/ash/api/api.ex b/lib/ash/api/api.ex index 568aec12..b4c2138a 100644 --- a/lib/ash/api/api.ex +++ b/lib/ash/api/api.ex @@ -904,7 +904,7 @@ defmodule Ash.Api do end 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} other -> other end @@ -939,48 +939,29 @@ defmodule Ash.Api do case subject do %Ash.Query{} = query -> - context = Map.put(context, :query, query) - - with {:ok, query, _} <- - Ash.Authorizer.add_calculations( - authorizer, - query, - authorizer_state, - context - ), - {:ok, new_filter} <- - Ash.Authorizer.alter_filter( - authorizer, - authorizer_state, - query.filter, - context - ), - {:ok, hydrated} <- - Ash.Filter.hydrate_refs(new_filter, %{ - resource: query.resource, - public?: false - }), - {:ok, new_sort} <- - Ash.Authorizer.alter_sort( - authorizer, - authorizer_state, - query.sort, - context - ) do - {:ok, true, %{query | filter: hydrated, sort: new_sort}} - end + alter_query(query, authorizer, authorizer_state, context) %Ash.Changeset{} = changeset -> context = Map.put(context, :changeset, changeset) - with {:ok, changeset, _} <- + with {:ok, changeset, authorizer_state} <- Ash.Authorizer.add_calculations( authorizer, changeset, authorizer_state, context ) do - {:ok, true, changeset} + 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 -> @@ -996,6 +977,39 @@ defmodule Ash.Api do defp alter_source(other, _, _, _, _), do: other + defp alter_query(query, authorizer, authorizer_state, context) do + context = Map.put(context, :query, query) + + with {:ok, query, _} <- + Ash.Authorizer.add_calculations( + authorizer, + query, + authorizer_state, + context + ), + {:ok, new_filter} <- + Ash.Authorizer.alter_filter( + authorizer, + authorizer_state, + query.filter, + context + ), + {:ok, hydrated} <- + Ash.Filter.hydrate_refs(new_filter, %{ + resource: query.resource, + public?: false + }), + {:ok, new_sort} <- + Ash.Authorizer.alter_sort( + authorizer, + authorizer_state, + query.sort, + context + ) do + {:ok, true, %{query | filter: hydrated, sort: new_sort}} + end + end + defp run_check(api, actor, subject, opts) do authorizers = Ash.Resource.Info.authorizers(subject.resource) diff --git a/lib/ash/filter/filter.ex b/lib/ash/filter/filter.ex index fc4f130d..e4af0564 100644 --- a/lib/ash/filter/filter.ex +++ b/lib/ash/filter/filter.ex @@ -601,6 +601,8 @@ defmodule Ash.Filter do {:_atomic_ref, field} when is_atom(field) -> if changeset do Ash.Changeset.atomic_ref(changeset, field) + else + {:_atomic_ref, field} end {:_context, fields} when is_list(fields) -> diff --git a/lib/ash/filter/runtime.ex b/lib/ash/filter/runtime.ex index ef186301..4de30642 100644 --- a/lib/ash/filter/runtime.ex +++ b/lib/ash/filter/runtime.ex @@ -720,6 +720,15 @@ defmodule Ash.Filter.Runtime do 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 try_cast_arguments(:var_args, args) do diff --git a/lib/ash/policy/authorizer/authorizer.ex b/lib/ash/policy/authorizer/authorizer.ex index 7ef242dd..9efc4e2c 100644 --- a/lib/ash/policy/authorizer/authorizer.ex +++ b/lib/ash/policy/authorizer/authorizer.ex @@ -1051,7 +1051,7 @@ defmodule Ash.Policy.Authorizer do {{check_module, check_opts}, true} -> result = try do - check_module.auto_filter(authorizer.actor, authorizer, check_opts) + nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts)) rescue e -> reraise Ash.Error.to_ash_error(e, __STACKTRACE__, @@ -1071,9 +1071,16 @@ defmodule Ash.Policy.Authorizer do result = try 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 - [not: check_module.auto_filter(authorizer.actor, authorizer, check_opts)] + [ + not: + nil_to_false( + check_module.auto_filter(authorizer.actor, authorizer, check_opts) + ) + ] end rescue e -> @@ -1103,6 +1110,9 @@ defmodule Ash.Policy.Authorizer do 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 "(#{print_tuple_boolean(l)} #{op} #{print_tuple_boolean(r)})" end @@ -1148,12 +1158,15 @@ defmodule Ash.Policy.Authorizer do {{check_module, check_opts}, required_status} -> additional_filter = 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 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 - [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 diff --git a/lib/ash/policy/check/changing_attributes.ex b/lib/ash/policy/check/changing_attributes.ex index 01f4c409..43f491a0 100644 --- a/lib/ash/policy/check/changing_attributes.ex +++ b/lib/ash/policy/check/changing_attributes.ex @@ -40,15 +40,29 @@ defmodule Ash.Policy.Check.ChangingAttributes do {:cont, expr} {{:ok, from}, {:ok, to}} -> - Ash.Expr.expr( - ^expr and not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to) - ) + if expr == true do + {:cont, + Ash.Expr.expr(not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to))} + else + {:cont, + Ash.Expr.expr( + ^expr and not (ref(attribute) == ^from and ^atomic_ref(attribute) == ^to) + )} + end {{:ok, from}, :error} -> - {:cont, Ash.Expr.expr(^expr and ref(attribute) != ^from)} + if expr == true do + {:cont, Ash.Expr.expr(ref(attribute) != ^from)} + else + {:cont, Ash.Expr.expr(^expr and ref(attribute) != ^from)} + end {:error, {:ok, to}} -> - {:cont, Ash.Expr.expr(^expr and ^atomic_ref(attribute) != ^to)} + if expr == true do + {:cont, Ash.Expr.expr(^atomic_ref(attribute) != ^to)} + else + {:cont, Ash.Expr.expr(^expr and ^atomic_ref(attribute) != ^to)} + end end else {:cont, expr}