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.
|
for an easy way to write that check.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@type actor :: any
|
||||||
@type options :: Keyword.t()
|
@type options :: Keyword.t()
|
||||||
@type authorizer :: Ash.Policy.Authorizer.t()
|
@type authorizer :: Ash.Policy.Authorizer.t()
|
||||||
@type check_type :: :simple | :filter | :manual
|
@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 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}`
|
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 """
|
@doc """
|
||||||
An optional callback, that allows the check to work with policies set to `access_type :filter`
|
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
|
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 """
|
@doc """
|
||||||
An optional callback, hat allows the check to work with policies set to `access_type :runtime`
|
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.
|
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())
|
list(Ash.Resource.record())
|
||||||
@doc "Describe the check in human readable format, given the options"
|
@doc "Describe the check in human readable format, given the options"
|
||||||
@callback describe(options()) :: String.t()
|
@callback describe(options()) :: String.t()
|
||||||
|
|
||||||
@callback requires_original_data?(struct(), options()) :: boolean()
|
@callback requires_original_data?(actor(), options()) :: boolean()
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
The type of the check
|
The type of the check
|
||||||
|
|
|
@ -268,4 +268,15 @@ defmodule Ash.Policy.Check.Builtins do
|
||||||
def changing_relationships(relationships) do
|
def changing_relationships(relationships) do
|
||||||
{Ash.Policy.Check.ChangingRelationships, relationships: relationships}
|
{Ash.Policy.Check.ChangingRelationships, relationships: relationships}
|
||||||
end
|
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
|
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(:resource) => Ash.Resource.t(),
|
||||||
required(:api) => Ash.Api.t(),
|
required(:api) => Ash.Api.t(),
|
||||||
optional(:query) => Ash.Query.t(),
|
optional(:query) => Ash.Query.t(),
|
||||||
optional(:changeset) => Ash.Query.t(),
|
optional(:changeset) => Ash.Changeset.t(),
|
||||||
optional(any) => any
|
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
|
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 :: %{
|
@type context :: %{
|
||||||
required(:action) => Ash.Resource.Actions.action(),
|
required(:action) => Ash.Resource.Actions.action(),
|
||||||
required(:resource) => Ash.Resource.t(),
|
required(:resource) => Ash.Resource.t(),
|
||||||
|
@ -15,7 +16,7 @@ defmodule Ash.Policy.SimpleCheck do
|
||||||
@type options :: Keyword.t()
|
@type options :: Keyword.t()
|
||||||
|
|
||||||
@doc "Whether or not the request matches the check"
|
@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
|
defmacro __using__(_) do
|
||||||
quote do
|
quote do
|
||||||
|
|
|
@ -31,6 +31,22 @@ defmodule Ash.Test.Policy.SimpleTest do
|
||||||
end
|
end
|
||||||
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
|
test "filter checks work on create/update/destroy actions", %{user: user} do
|
||||||
user2 = Api.create!(Ash.Changeset.new(User))
|
user2 = Api.create!(Ash.Changeset.new(User))
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,10 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
||||||
create :create_foo do
|
create :create_foo do
|
||||||
argument :foo, :string
|
argument :foo, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create :create_bar do
|
||||||
|
argument :bar, :integer, allow_nil?: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
|
@ -36,6 +40,14 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
||||||
policy action(:create_foo) do
|
policy action(:create_foo) do
|
||||||
authorize_if expr(is_foo(foo: arg(:foo)))
|
authorize_if expr(is_foo(foo: arg(:foo)))
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
calculations do
|
calculations do
|
||||||
|
@ -49,4 +61,8 @@ defmodule Ash.Test.Support.PolicySimple.Tweet do
|
||||||
attribute_writable? true
|
attribute_writable? true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp check_bar_is_big(_actor, context) do
|
||||||
|
context.changeset.arguments.bar > 5
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue