mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
feat: add can?/4 policy utility (#349)
* feat: add can?/4 policy utility * refactor: change defaults and add can/4 * refactor: add typespecs, rename test
This commit is contained in:
parent
4dad1e5d7d
commit
3776af9f85
2 changed files with 85 additions and 0 deletions
|
@ -133,6 +133,79 @@ defmodule Ash.Policy.Info do
|
|||
Extension.get_opt(resource, [:policies], :default_access_type, :strict, false)
|
||||
end
|
||||
|
||||
@doc "A utility to determine if an actor is authorized for a given action."
|
||||
@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
|
||||
opts = Keyword.put(opts, :maybe_is, Keyword.get(opts, :maybe_is, false))
|
||||
|
||||
can(resource, action_or_action_name, actor, opts)
|
||||
end
|
||||
|
||||
@doc "A utility to determine if an actor is authorized for a given action."
|
||||
@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
|
||||
api = Keyword.fetch!(opts, :api)
|
||||
maybe_is = Keyword.get(opts, :maybe_is, :maybe)
|
||||
|
||||
action =
|
||||
case action_or_action_name do
|
||||
%Ash.Resource.Actions.Create{} = action -> action
|
||||
%Ash.Resource.Actions.Read{} = action -> action
|
||||
%Ash.Resource.Actions.Update{} = action -> action
|
||||
%Ash.Resource.Actions.Destroy{} = action -> action
|
||||
name when is_atom(name) -> Ash.Resource.Info.action(resource, name)
|
||||
end
|
||||
|
||||
# Get action type from resource
|
||||
case action.type do
|
||||
:update ->
|
||||
query =
|
||||
struct(resource)
|
||||
|> Ash.Changeset.new(%{})
|
||||
|> Ash.Changeset.for_update(action.name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:create ->
|
||||
query =
|
||||
resource
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.for_create(action.name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:read ->
|
||||
query = Ash.Query.for_read(resource, action.name)
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
:destroy ->
|
||||
query =
|
||||
struct(resource)
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.for_destroy(action.name)
|
||||
|
||||
run_check(actor, query, api: api, maybe_is: maybe_is)
|
||||
|
||||
action_type ->
|
||||
raise ArgumentError, message: "Invalid action type \"#{action_type}\""
|
||||
end
|
||||
end
|
||||
|
||||
defp run_check(actor, query, api: api, maybe_is: maybe_is) do
|
||||
case Ash.Policy.Info.strict_check(actor, query, api) do
|
||||
true ->
|
||||
true
|
||||
|
||||
:maybe ->
|
||||
maybe_is
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This should be done at compile time
|
||||
defp set_access_type(policies, default) when is_list(policies) do
|
||||
Enum.map(policies, &set_access_type(&1, default))
|
||||
|
|
|
@ -40,6 +40,18 @@ defmodule Ash.Policy.Test.RbacTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "if the action 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")
|
||||
|
||||
assert Ash.Policy.Info.can(File, :read, 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