mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: add matches
built-in policy check (#937)
This commit is contained in:
parent
c04b638136
commit
ed4655cd83
7 changed files with 65 additions and 6 deletions
|
@ -9,6 +9,7 @@ defmodule Ash.Policy.Check do
|
|||
for an easy way to write that check.
|
||||
"""
|
||||
|
||||
@type actor :: any
|
||||
@type options :: Keyword.t()
|
||||
@type authorizer :: Ash.Policy.Authorizer.t()
|
||||
@type check_type :: :simple | :filter | :manual
|
||||
|
@ -29,24 +30,24 @@ defmodule Ash.Policy.Check do
|
|||
It should return `{:ok, true}` if it can tell that the request is authorized, and `{:ok, false}` if
|
||||
it can tell that it is not. If unsure, it should return `{:ok, :unknown}`
|
||||
"""
|
||||
@callback strict_check(struct(), authorizer(), options) :: {:ok, boolean | :unknown}
|
||||
@callback strict_check(actor(), authorizer(), options) :: {:ok, boolean | :unknown}
|
||||
@doc """
|
||||
An optional callback, that allows the check to work with policies set to `access_type :filter`
|
||||
|
||||
Return a keyword list filter that will be applied to the query being made, and will scope the results to match the rule
|
||||
"""
|
||||
@callback auto_filter(struct(), authorizer(), options()) :: Keyword.t() | Ash.Expr.t()
|
||||
@callback auto_filter(actor(), authorizer(), options()) :: Keyword.t() | Ash.Expr.t()
|
||||
@doc """
|
||||
An optional callback, hat allows the check to work with policies set to `access_type :runtime`
|
||||
|
||||
Takes a list of records, and returns the subset of authorized records.
|
||||
"""
|
||||
@callback check(struct(), list(Ash.Resource.record()), map, options) ::
|
||||
@callback check(actor(), list(Ash.Resource.record()), map, options) ::
|
||||
list(Ash.Resource.record())
|
||||
@doc "Describe the check in human readable format, given the options"
|
||||
@callback describe(options()) :: String.t()
|
||||
|
||||
@callback requires_original_data?(struct(), options()) :: boolean()
|
||||
@callback requires_original_data?(actor(), options()) :: boolean()
|
||||
|
||||
@doc """
|
||||
The type of the check
|
||||
|
|
|
@ -268,4 +268,15 @@ defmodule Ash.Policy.Check.Builtins do
|
|||
def changing_relationships(relationships) do
|
||||
{Ash.Policy.Check.ChangingRelationships, relationships: relationships}
|
||||
end
|
||||
|
||||
@doc "This check is true when the specified function returns true"
|
||||
defmacro matches(description, func) do
|
||||
{value, function} = Spark.CodeHelpers.lift_functions(func, :matches_policy_check, __CALLER__)
|
||||
|
||||
quote generated: true do
|
||||
unquote(function)
|
||||
|
||||
{Ash.Policy.Check.Matches, description: unquote(description), func: unquote(value)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
lib/ash/policy/check/matches.ex
Normal file
14
lib/ash/policy/check/matches.ex
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Ash.Policy.Check.Matches do
|
||||
@moduledoc "This check is true when the specified function returns true"
|
||||
use Ash.Policy.SimpleCheck
|
||||
|
||||
@impl true
|
||||
def describe(options) do
|
||||
options[:description]
|
||||
end
|
||||
|
||||
@impl true
|
||||
def match?(actor, request, options) do
|
||||
options[:func].(actor, request)
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@ defmodule Ash.Policy.FilterCheckWithContext do
|
|||
required(:resource) => Ash.Resource.t(),
|
||||
required(:api) => Ash.Api.t(),
|
||||
optional(:query) => Ash.Query.t(),
|
||||
optional(:changeset) => Ash.Query.t(),
|
||||
optional(:changeset) => Ash.Changeset.t(),
|
||||
optional(any) => any
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Ash.Policy.SimpleCheck do
|
|||
|
||||
Define `c:match?/3`, which gets the actor, request context, and opts, and returns true or false
|
||||
"""
|
||||
@type actor :: Ash.Policy.Check.actor()
|
||||
@type context :: %{
|
||||
required(:action) => Ash.Resource.Actions.action(),
|
||||
required(:resource) => Ash.Resource.t(),
|
||||
|
@ -15,7 +16,7 @@ defmodule Ash.Policy.SimpleCheck do
|
|||
@type options :: Keyword.t()
|
||||
|
||||
@doc "Whether or not the request matches the check"
|
||||
@callback match?(actor :: struct(), context(), options) :: boolean
|
||||
@callback match?(actor(), context(), options()) :: boolean
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
|
|
@ -31,6 +31,22 @@ defmodule Ash.Test.Policy.SimpleTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "functions can be used as checks through `matches`", %{user: user} do
|
||||
Tweet
|
||||
|> Ash.Changeset.for_create(:create_bar, %{bar: 2}, actor: user)
|
||||
|> Api.create!()
|
||||
|
||||
Tweet
|
||||
|> Ash.Changeset.for_create(:create_bar, %{bar: 9}, actor: user)
|
||||
|> Api.create!()
|
||||
|
||||
assert_raise Ash.Error.Forbidden, fn ->
|
||||
Tweet
|
||||
|> Ash.Changeset.for_create(:create_bar, %{bar: 1}, 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))
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
|||
create :create_foo do
|
||||
argument :foo, :string
|
||||
end
|
||||
|
||||
create :create_bar do
|
||||
argument :bar, :integer, allow_nil?: false
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
|
@ -36,6 +40,14 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
|||
policy action(:create_foo) do
|
||||
authorize_if expr(is_foo(foo: arg(:foo)))
|
||||
end
|
||||
|
||||
policy action(:create_bar) do
|
||||
authorize_if matches("bar is big", &check_bar_is_big/2)
|
||||
|
||||
authorize_if matches("bar is even", fn _actor, context ->
|
||||
rem(context.changeset.arguments.bar, 2) == 0
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
calculations do
|
||||
|
@ -49,4 +61,8 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
|||
attribute_writable? true
|
||||
end
|
||||
end
|
||||
|
||||
defp check_bar_is_big(_actor, context) do
|
||||
context.changeset.arguments.bar > 5
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue