mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
fix: allow argument references in policies
This commit is contained in:
parent
007e0fb081
commit
005c1bc6c1
6 changed files with 83 additions and 17 deletions
|
@ -11,21 +11,23 @@ defmodule Ash.Error.Forbidden.CannotFilterCreates do
|
|||
|
||||
def message(_) do
|
||||
"""
|
||||
Filter checks cannot be used with create actions.
|
||||
Cannot use a filter to authorize a create.
|
||||
|
||||
If you are using Ash.Policy.Authorizer:
|
||||
|
||||
To solve for this, use other checks, or write a custom check.
|
||||
|
||||
Many expressions, like those that reference relationships, require using custom checks for create actions.
|
||||
|
||||
Expressions that only reference the actor or context, for example `expr(^actor(:is_admin) == true)` will work fine.
|
||||
Expressions that only reference the actor or context, for example `expr(^actor(:is_admin) == true)` will work
|
||||
because those are evaluated without needing to reference data.
|
||||
|
||||
For create actions, there is no data yet. In the future we may support referencing simple attributes and those
|
||||
references will be referring to the values of the data about to be created, but at this time we do not.
|
||||
|
||||
Given a policy like:
|
||||
|
||||
```elixir
|
||||
policy expr(special == true) do
|
||||
authorize_if expr(allows_special == true)
|
||||
authorize_if expr(allows_special == true)
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -33,7 +35,7 @@ defmodule Ash.Error.Forbidden.CannotFilterCreates do
|
|||
|
||||
```elixir
|
||||
policy [expr(special == true), action_type([:read, :update, :destroy])] do
|
||||
authorize_if expr(allows_special == true)
|
||||
authorize_if expr(allows_special == true)
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -41,7 +43,7 @@ defmodule Ash.Error.Forbidden.CannotFilterCreates do
|
|||
|
||||
```elixir
|
||||
policy [changing_attributes(special: [to: true]), action_type(:create)] do
|
||||
authorize_if changing_attributes(special: [to: true])
|
||||
authorize_if changing_attributes(special: [to: true])
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
@ -4,11 +4,18 @@ defmodule Ash.Policy.Check.Action do
|
|||
|
||||
@impl true
|
||||
def describe(options) do
|
||||
"action == #{inspect(options[:action])}"
|
||||
operator =
|
||||
if is_list(options[:action]) do
|
||||
"in"
|
||||
else
|
||||
"=="
|
||||
end
|
||||
|
||||
"action #{operator} #{inspect(options[:action])}"
|
||||
end
|
||||
|
||||
@impl true
|
||||
def match?(_actor, %{action: %{name: name}}, options) do
|
||||
name == options[:action]
|
||||
name in List.wrap(options[:action])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,7 +64,7 @@ defmodule Ash.Policy.FilterCheck do
|
|||
|
||||
opts
|
||||
|> filter()
|
||||
|> Ash.Filter.build_filter_from_template(actor)
|
||||
|> Ash.Filter.build_filter_from_template(actor, Ash.Policy.FilterCheck.args(authorizer))
|
||||
|> try_eval(authorizer)
|
||||
|> case do
|
||||
{:ok, false} ->
|
||||
|
@ -165,12 +165,22 @@ defmodule Ash.Policy.FilterCheck do
|
|||
|
||||
def auto_filter(actor, authorizer, opts) do
|
||||
opts = Keyword.put_new(opts, :resource, authorizer.resource)
|
||||
Ash.Filter.build_filter_from_template(filter(opts), actor)
|
||||
|
||||
Ash.Filter.build_filter_from_template(
|
||||
filter(opts),
|
||||
actor,
|
||||
Ash.Policy.FilterCheck.args(authorizer)
|
||||
)
|
||||
end
|
||||
|
||||
def auto_filter_not(actor, authorizer, opts) do
|
||||
opts = Keyword.put_new(opts, :resource, authorizer.resource)
|
||||
Ash.Filter.build_filter_from_template(reject(opts), actor)
|
||||
|
||||
Ash.Filter.build_filter_from_template(
|
||||
reject(opts),
|
||||
actor,
|
||||
Ash.Policy.FilterCheck.args(authorizer)
|
||||
)
|
||||
end
|
||||
|
||||
def reject(opts) do
|
||||
|
@ -211,4 +221,15 @@ defmodule Ash.Policy.FilterCheck do
|
|||
def is_filter_check?(module) do
|
||||
:erlang.function_exported(module, :filter, 1)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def args(%{changeset: %{arguments: arguments}}) do
|
||||
arguments
|
||||
end
|
||||
|
||||
def args(%{query: %{arguments: arguments}}) do
|
||||
arguments
|
||||
end
|
||||
|
||||
def args(_), do: %{}
|
||||
end
|
||||
|
|
|
@ -48,7 +48,7 @@ defmodule Ash.Policy.FilterCheckWithContext do
|
|||
|
||||
actor
|
||||
|> filter(authorizer, opts)
|
||||
|> Ash.Filter.build_filter_from_template(actor)
|
||||
|> Ash.Filter.build_filter_from_template(actor, Ash.Policy.FilterCheck.args(authorizer))
|
||||
|> try_eval(authorizer)
|
||||
|> case do
|
||||
{:ok, false} ->
|
||||
|
@ -149,12 +149,22 @@ defmodule Ash.Policy.FilterCheckWithContext do
|
|||
|
||||
def auto_filter(actor, authorizer, opts) do
|
||||
opts = Keyword.put_new(opts, :resource, authorizer.resource)
|
||||
Ash.Filter.build_filter_from_template(filter(actor, authorizer, opts), actor)
|
||||
|
||||
Ash.Filter.build_filter_from_template(
|
||||
filter(actor, authorizer, opts),
|
||||
actor,
|
||||
Ash.Policy.FilterCheck.args(authorizer)
|
||||
)
|
||||
end
|
||||
|
||||
def auto_filter_not(actor, authorizer, opts) do
|
||||
opts = Keyword.put_new(opts, :resource, authorizer.resource)
|
||||
Ash.Filter.build_filter_from_template(reject(actor, authorizer, opts), actor)
|
||||
|
||||
Ash.Filter.build_filter_from_template(
|
||||
reject(actor, authorizer, opts),
|
||||
actor,
|
||||
Ash.Policy.FilterCheck.args(authorizer)
|
||||
)
|
||||
end
|
||||
|
||||
def reject(actor, authorizer, opts) do
|
||||
|
|
|
@ -19,6 +19,18 @@ defmodule Ash.Test.Policy.SimpleTest do
|
|||
assert [] = Api.read!(Tweet, actor: user)
|
||||
end
|
||||
|
||||
test "arguments can be referenced in expression policies", %{admin: admin, user: user} do
|
||||
Tweet
|
||||
|> Ash.Changeset.for_create(:create_foo, %{foo: "foo", user_id: admin.id}, actor: user)
|
||||
|> Api.create!()
|
||||
|
||||
assert_raise Ash.Error.Forbidden, fn ->
|
||||
Tweet
|
||||
|> Ash.Changeset.for_create(:create_foo, %{foo: "bar", user_id: admin.id}, actor: user)
|
||||
|> Api.create!()
|
||||
end
|
||||
end
|
||||
|
||||
test "filter checks work on create/update/destroy actions", %{user: user} do
|
||||
user2 = Api.create!(Ash.Changeset.new(User))
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
|||
|
||||
actions do
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
create :create_foo do
|
||||
argument :foo, :string
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
|
@ -21,12 +25,22 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
|||
authorize_if always()
|
||||
end
|
||||
|
||||
policy always() do
|
||||
policy action_type([:read, :update, :destroy]) do
|
||||
authorize_if(expr(user_id == ^actor(:id)))
|
||||
end
|
||||
|
||||
policy action(:create) do
|
||||
authorize_if relating_to_actor(:user)
|
||||
end
|
||||
|
||||
policy action(:create_foo) do
|
||||
authorize_if expr(^arg(:foo) == "foo")
|
||||
end
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :user, Ash.Test.Support.PolicySimple.User
|
||||
belongs_to :user, Ash.Test.Support.PolicySimple.User do
|
||||
attribute_writable? true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue