mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: allow passing query or changeset in can/can?/4 (#410)
This commit is contained in:
parent
ae8e0e356b
commit
62805fcce9
2 changed files with 68 additions and 19 deletions
|
@ -146,15 +146,20 @@ defmodule Ash.Policy.Info do
|
|||
See the documentation of `can/4` for more.
|
||||
"""
|
||||
@type can_option? :: {:api, module} | {:maybe_is, boolean()}
|
||||
@spec can?(Ash.Resource.t(), atom(), map() | nil, list(can_option?())) :: boolean()
|
||||
def can?(resource, action_or_action_name, actor, opts \\ []) do
|
||||
@spec can?(
|
||||
Ash.Resource.t(),
|
||||
atom() | Ash.Resource.Actions.action() | Ash.Query.t() | Ash.Changeset.t(),
|
||||
map() | nil,
|
||||
list(can_option?())
|
||||
) :: boolean()
|
||||
def can?(resource, action_or_query_or_changeset, actor, opts \\ []) do
|
||||
opts = Keyword.put(opts, :maybe_is, Keyword.get(opts, :maybe_is, false))
|
||||
|
||||
can(resource, action_or_action_name, actor, opts)
|
||||
can(resource, action_or_query_or_changeset, actor, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
A utility to determine if an actor is or may be authorized for a given action.
|
||||
A utility to determine if an actor is or may be authorized for a given action/query/changeset.
|
||||
|
||||
This only runs the "strict check" portion of policies, meaning that it can return `:maybe` in some cases.
|
||||
If you have `access_type :runtime` in any of your policies, then you may get `:maybe` from this function.
|
||||
|
@ -166,13 +171,20 @@ defmodule Ash.Policy.Info do
|
|||
returned, and this function would return `true`.
|
||||
"""
|
||||
@type can_option :: {:api, module} | {:maybe_is, boolean() | :maybe}
|
||||
@spec can(Ash.Resource.t(), atom(), map() | nil, list(can_option())) :: boolean() | :maybe
|
||||
def can(resource, action_or_action_name, actor, opts \\ []) do
|
||||
@spec can(
|
||||
Ash.Resource.t(),
|
||||
atom() | Ash.Resource.Actions.action() | Ash.Query.t() | Ash.Changeset.t(),
|
||||
map() | nil,
|
||||
list(can_option())
|
||||
) :: boolean() | :maybe
|
||||
def can(resource, action_or_query_or_changeset, actor, opts \\ []) do
|
||||
api = Keyword.fetch!(opts, :api)
|
||||
maybe_is = Keyword.get(opts, :maybe_is, :maybe)
|
||||
|
||||
action =
|
||||
case action_or_action_name do
|
||||
action_or_query_or_changeset =
|
||||
case action_or_query_or_changeset do
|
||||
%Ash.Query{} = query -> query
|
||||
%Ash.Changeset{} = changeset -> changeset
|
||||
%Ash.Resource.Actions.Create{} = action -> action
|
||||
%Ash.Resource.Actions.Read{} = action -> action
|
||||
%Ash.Resource.Actions.Update{} = action -> action
|
||||
|
@ -181,37 +193,44 @@ defmodule Ash.Policy.Info do
|
|||
end
|
||||
|
||||
# Get action type from resource
|
||||
case action.type do
|
||||
:update ->
|
||||
case action_or_query_or_changeset do
|
||||
%Ash.Query{} = query ->
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
%Ash.Changeset{} = changeset ->
|
||||
run_check(actor, changeset, api: api, maybe_is: maybe_is)
|
||||
|
||||
%{type: :update, name: name} ->
|
||||
query =
|
||||
struct(resource)
|
||||
|> Ash.Changeset.new(%{})
|
||||
|> Ash.Changeset.for_update(action.name)
|
||||
|> Ash.Changeset.for_update(name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:create ->
|
||||
%{type: :create, name: name} ->
|
||||
query =
|
||||
resource
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.for_create(action.name)
|
||||
|> Ash.Changeset.for_create(name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:read ->
|
||||
query = Ash.Query.for_read(resource, action.name)
|
||||
%{type: :read, name: name} ->
|
||||
query = Ash.Query.for_read(resource, name)
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:destroy ->
|
||||
%{type: :destroy, name: name} ->
|
||||
query =
|
||||
struct(resource)
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.for_destroy(action.name)
|
||||
|> Ash.Changeset.for_destroy(name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
action_type ->
|
||||
raise ArgumentError, message: "Invalid action type \"#{action_type}\""
|
||||
_ ->
|
||||
raise ArgumentError,
|
||||
message: "Invalid action/query/changeset \"#{inspect(action_or_query_or_changeset)}\""
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -52,6 +52,36 @@ defmodule Ash.Test.Policy.RbacTest do
|
|||
assert Ash.Policy.Info.can(File, :read, user, api: Api)
|
||||
end
|
||||
|
||||
test "if the query can be performed, the can utility should return true", %{
|
||||
user: user,
|
||||
org: org
|
||||
} do
|
||||
file_with_access = create_file(org, "foo")
|
||||
give_role(user, org, :viewer, :file, file_with_access.id)
|
||||
create_file(org, "bar")
|
||||
create_file(org, "baz")
|
||||
|
||||
query = Ash.Query.for_read(File, :read)
|
||||
|
||||
assert Ash.Policy.Info.can(File, query, user, api: Api)
|
||||
end
|
||||
|
||||
test "if the changeset can be performed, the can utility should return true", %{
|
||||
user: user,
|
||||
org: org
|
||||
} do
|
||||
file_with_access = create_file(org, "foo")
|
||||
give_role(user, org, :viewer, :file, file_with_access.id)
|
||||
|
||||
changeset =
|
||||
File
|
||||
|> Ash.Changeset.new(%{name: "bar"})
|
||||
|> Ash.Changeset.for_create(:create)
|
||||
|> Ash.Changeset.manage_relationship(:organization, org, type: :append_and_remove)
|
||||
|
||||
assert Ash.Policy.Info.can(File, changeset, user, api: Api)
|
||||
end
|
||||
|
||||
defp give_role(user, org, role, resource, resource_id) do
|
||||
Membership
|
||||
|> Ash.Changeset.new(%{role: role, resource: resource, resource_id: resource_id})
|
||||
|
|
Loading…
Reference in a new issue