mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
WIP
This commit is contained in:
parent
9bb19f95ad
commit
ebd8291631
34 changed files with 444 additions and 880 deletions
|
@ -28,12 +28,12 @@ defmodule Post do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: user_is(:admin)
|
||||
]
|
||||
|
||||
create :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: user_is(:admin)
|
||||
]
|
||||
|
||||
|
@ -151,3 +151,5 @@ end
|
|||
* We need to validate incoming attributes/relationships better.
|
||||
* Side load authorization testing
|
||||
* Validate that params on the way in are either all strings or all atoms
|
||||
* Make it `rules: :none` (or something better) than `rules: false`
|
||||
* Support `read_rules`, `create_rules`, `update_rules` for attributes/relationships
|
||||
|
|
|
@ -6,17 +6,17 @@ defmodule Ash.Actions.Attributes do
|
|||
attribute.name in Map.get(changeset, :__ash_skip_authorization_fields__, [])
|
||||
end)
|
||||
|> Enum.filter(fn attribute ->
|
||||
attribute.authorization_steps != false && Map.has_key?(changeset.changes, attribute.name)
|
||||
attribute.write_rules != false && Map.has_key?(changeset.changes, attribute.name)
|
||||
end)
|
||||
|> Enum.map(fn attribute ->
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
authorization_steps: attribute.authorization_steps,
|
||||
rules: attribute.write_rules,
|
||||
resource: resource,
|
||||
changeset: changeset,
|
||||
action_type: action.type,
|
||||
dependencies: [[:data]],
|
||||
fetcher: fn %{data: data} -> {:ok, data} end,
|
||||
fetcher: fn _, %{data: data} -> {:ok, data} end,
|
||||
state_key: :data,
|
||||
relationship: [],
|
||||
source: "change on `#{attribute.name}`"
|
||||
|
|
|
@ -59,15 +59,15 @@ defmodule Ash.Actions.Create do
|
|||
create_authorization_request =
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
authorization_steps: action.authorization_steps,
|
||||
rules: action.rules,
|
||||
resource: resource,
|
||||
changeset:
|
||||
Ash.Actions.Relationships.authorization_changeset(
|
||||
Relationships.authorization_changeset(
|
||||
changeset,
|
||||
relationships
|
||||
),
|
||||
action_type: action.type,
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn changeset, _ ->
|
||||
Ash.DataLayer.create(resource, changeset)
|
||||
end,
|
||||
dependencies: Map.get(changeset, :__changes_depend_on__) || [],
|
||||
|
@ -80,7 +80,16 @@ defmodule Ash.Actions.Create do
|
|||
attribute_requests =
|
||||
Attributes.attribute_change_authorizations(changeset, api, resource, action)
|
||||
|
||||
relationship_auths = Map.get(changeset, :__authorizations__, [])
|
||||
relationship_read_auths = Map.get(changeset, :__authorizations__, [])
|
||||
|
||||
relationship_change_auths =
|
||||
Relationships.relationship_change_authorizations(
|
||||
changeset,
|
||||
api,
|
||||
resource,
|
||||
action,
|
||||
relationships
|
||||
)
|
||||
|
||||
if params[:authorization] do
|
||||
strict_access? =
|
||||
|
@ -91,7 +100,8 @@ defmodule Ash.Actions.Create do
|
|||
|
||||
Authorizer.authorize(
|
||||
params[:authorization][:user],
|
||||
[create_authorization_request | attribute_requests] ++ relationship_auths,
|
||||
[create_authorization_request | attribute_requests] ++
|
||||
relationship_read_auths ++ relationship_change_auths,
|
||||
strict_access?: strict_access?,
|
||||
log_final_report?: params[:authorization][:log_final_report?] || false
|
||||
)
|
||||
|
@ -100,7 +110,8 @@ defmodule Ash.Actions.Create do
|
|||
|
||||
Authorizer.authorize(
|
||||
authorization[:user],
|
||||
[create_authorization_request | attribute_requests] ++ relationship_auths,
|
||||
[create_authorization_request | attribute_requests] ++
|
||||
relationship_read_auths ++ relationship_change_auths,
|
||||
fetch_only?: true
|
||||
)
|
||||
end
|
||||
|
|
|
@ -27,7 +27,7 @@ defmodule Ash.Actions.Destroy do
|
|||
auth_request =
|
||||
Ash.Authorization.Request.new(
|
||||
resource: resource,
|
||||
authorization_steps: action.authorization_steps,
|
||||
rules: action.rules,
|
||||
destroy: record,
|
||||
source: "destroy request"
|
||||
)
|
||||
|
|
|
@ -40,10 +40,10 @@ defmodule Ash.Actions.Read do
|
|||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
resource: resource,
|
||||
authorization_steps: action.authorization_steps,
|
||||
rules: action.rules,
|
||||
filter: filter,
|
||||
action_type: action.type,
|
||||
fetcher: fn _ -> Ash.DataLayer.run_query(query, resource) end,
|
||||
fetcher: fn _, _ -> Ash.DataLayer.run_query(query, resource) end,
|
||||
must_fetch?: true,
|
||||
state_key: :data,
|
||||
relationship: [],
|
||||
|
|
|
@ -67,6 +67,35 @@ defmodule Ash.Actions.Relationships do
|
|||
end)
|
||||
end
|
||||
|
||||
def relationship_change_authorizations(changeset, api, resource, action, relationships) do
|
||||
Enum.flat_map(relationships, fn {relationship_name, _data} ->
|
||||
case Ash.relationship(resource, relationship_name) do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
relationship ->
|
||||
authorization =
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
rules: relationship.write_rules,
|
||||
resource: resource,
|
||||
changeset: authorization_changeset(changeset, relationships),
|
||||
action_type: action.type,
|
||||
fetcher: fn _, %{data: data} ->
|
||||
{:ok, data}
|
||||
end,
|
||||
dependencies: [:data | Map.get(changeset, :__changes_depend_on__, [])],
|
||||
state_key: :data,
|
||||
must_fetch?: false,
|
||||
relationship: [],
|
||||
source: "#{relationship_name} edit"
|
||||
)
|
||||
|
||||
[authorization]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_relationship_read_authorizations(changeset, api, relationship, input) do
|
||||
changeset
|
||||
|> add_replace_authorizations(api, relationship, input)
|
||||
|
@ -177,13 +206,13 @@ defmodule Ash.Actions.Relationships do
|
|||
authorization =
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
authorization_steps: default_read.authorization_steps,
|
||||
rules: default_read.rules,
|
||||
resource: relationship.destination,
|
||||
action_type: :read,
|
||||
filter: filter,
|
||||
must_fetch?: true,
|
||||
state_key: [:relationships, relationship_name, type],
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn _, _ ->
|
||||
case api.read(destination, filter: filter, paginate: false) do
|
||||
{:ok, %{results: results}} -> {:ok, results}
|
||||
{:error, error} -> {:error, error}
|
||||
|
@ -303,48 +332,84 @@ defmodule Ash.Actions.Relationships do
|
|||
end
|
||||
|
||||
def authorization_changeset(changeset, relationships) do
|
||||
# relationships_needing_field_update =
|
||||
# relationships
|
||||
# |> Enum.reduce_while({:ok, []}, fn {name, data}, {:ok, relationships} ->
|
||||
# case Ash.relationship(changeset.data.__struct__, name) do
|
||||
# nil ->
|
||||
# {:halt, {:error, name}}
|
||||
|
||||
# %{type: :belongs_to, destination_field: destination_field} = relationship ->
|
||||
# case identifier_or_identifiers(relationship, data) do
|
||||
# {:ok, identifier} ->
|
||||
# if Keyword.has_key?(identifier, destination_field) do
|
||||
# {:cont, {:ok, relationships}}
|
||||
# else
|
||||
# {:cont, {:ok, [relationship | relationships]}}
|
||||
# end
|
||||
|
||||
# {:error, _} ->
|
||||
# {:halt, {:error, name}}
|
||||
# end
|
||||
|
||||
# _ ->
|
||||
# {:cont, {:ok, relationships}}
|
||||
# end
|
||||
# end)
|
||||
if relationships == %{} do
|
||||
changeset
|
||||
else
|
||||
fn data ->
|
||||
Enum.reduce_while(data.relationships, {:ok, changeset}, fn {relationship,
|
||||
relationship_data},
|
||||
{:ok, changeset} ->
|
||||
data
|
||||
|> Map.get(:relationships, %{})
|
||||
|> Enum.reduce(changeset, fn {relationship, relationship_data}, changeset ->
|
||||
relationship = Ash.relationship(changeset.data.__struct__, relationship)
|
||||
|
||||
case add_relationship_to_changeset(changeset, relationship, relationship_data) do
|
||||
{:ok, changeset} -> {:cont, {:ok, changeset}}
|
||||
{:error, error} -> {:halt, {:error, error}}
|
||||
end
|
||||
add_relationship_to_changeset(changeset, relationship, relationship_data)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp add_relationship_to_changeset(
|
||||
changeset,
|
||||
%{type: :belongs_to, destination: destination} = relationship,
|
||||
relationship_data
|
||||
) do
|
||||
pkey = Ash.primary_key(destination)
|
||||
|
||||
case relationship_data do
|
||||
%{current: [], replace: [new]} ->
|
||||
changeset
|
||||
|> Ecto.Changeset.put_change(
|
||||
relationship.source_field,
|
||||
Map.get(new, relationship.destination_field)
|
||||
)
|
||||
|> add_relationship_change_metadata(relationship.name, %{add: [new]})
|
||||
|
||||
%{current: [current], replace: []} ->
|
||||
changeset
|
||||
|> Ecto.Changeset.put_change(
|
||||
relationship.source_field,
|
||||
nil
|
||||
)
|
||||
|> add_relationship_change_metadata(relationship.name, %{remove: [current]})
|
||||
|
||||
%{current: [current], replace: [new]} ->
|
||||
changeset
|
||||
|> Ecto.Changeset.put_change(
|
||||
relationship.source_field,
|
||||
Map.get(new, relationship.destination_field)
|
||||
)
|
||||
|> add_relationship_change_metadata(relationship.name, %{remove: [current], add: [new]})
|
||||
|
||||
%{current: [current], add: [add]} ->
|
||||
if Map.take(current, pkey) == Map.take(add, pkey) do
|
||||
changeset
|
||||
else
|
||||
Ecto.Changeset.add_error(
|
||||
changeset,
|
||||
relationship.name,
|
||||
"Can't add a value to a belongs to when something is already related."
|
||||
)
|
||||
end
|
||||
|
||||
%{current: [], remove: [_]} ->
|
||||
Ecto.Changeset.add_error(
|
||||
changeset,
|
||||
relationship.name,
|
||||
"Can't remove a value from a belongs to when nothing is related"
|
||||
)
|
||||
|
||||
%{current: [current], remove: [remove]} ->
|
||||
if Map.take(current, pkey) == Map.take(remove, pkey) do
|
||||
Ecto.Changeset.put_change(changeset, relationship.source_field, nil)
|
||||
else
|
||||
Ecto.Changeset.add_error(
|
||||
changeset,
|
||||
relationship.name,
|
||||
"Can't remove a related value if a different record is related"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp add_relationship_to_changeset(changeset, relationship, relationship_data) do
|
||||
IO.inspect(relationship, label: "relationship")
|
||||
IO.inspect(relationship_data, label: "relationship data")
|
||||
|
@ -352,42 +417,14 @@ defmodule Ash.Actions.Relationships do
|
|||
{:ok, changeset}
|
||||
end
|
||||
|
||||
# defp do_authorization_changeset(changeset, []) do
|
||||
# changeset
|
||||
# end
|
||||
|
||||
# defp do_authorization_changeset(
|
||||
# changeset,
|
||||
# relationships_being_changed
|
||||
# ) do
|
||||
# fn data ->
|
||||
# Enum.reduce(relationships_being_changed, changeset, fn relationship, changeset ->
|
||||
|
||||
# end)
|
||||
# # changeset
|
||||
# # changeset
|
||||
# # |> update_relationship_fields(data, relationships_needing_field_update)
|
||||
# # |> split_relationships_being_replaced(data, relationships_being_replaced)
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp update_relationship_fields(changeset, data, relationships_needing_field_update) do
|
||||
# Enum.reduce(relationships_needing_field_update, changeset, fn relationship, changeset ->
|
||||
# related = get_in(data, [:relationships, relationship.name, :to_add])
|
||||
|
||||
# if related do
|
||||
# value = Map.get(related, relationship.destination_field)
|
||||
# Ecto.Changeset.cast(changeset, relationship.source_field, value)
|
||||
# else
|
||||
# Ecto.Changeset.add_error(changeset, relationship.name, "Invalid relationship data")
|
||||
# end
|
||||
# end)
|
||||
# end
|
||||
|
||||
# defp split_relationships_being_replaced(changeset, data, relationships_being_replaced) do
|
||||
# # TODO: this
|
||||
# changeset
|
||||
# end
|
||||
defp add_relationship_change_metadata(changeset, relationship_name, data) do
|
||||
Map.update(
|
||||
changeset,
|
||||
:__ash_relationships__,
|
||||
%{relationship_name => data},
|
||||
&Map.put(&1, relationship_name, data)
|
||||
)
|
||||
end
|
||||
|
||||
defp fetch_string_or_atom(map, name) do
|
||||
case Map.fetch(map, name) do
|
||||
|
@ -396,101 +433,6 @@ defmodule Ash.Actions.Relationships do
|
|||
end
|
||||
end
|
||||
|
||||
# defp read_to_relate_authorization(
|
||||
# api,
|
||||
# %{destination: destination} = relationship,
|
||||
# value
|
||||
# ) do
|
||||
# default_read =
|
||||
# Ash.primary_action(destination, :read) ||
|
||||
# raise "Need a default read action for #{destination}"
|
||||
|
||||
# relationship_name = relationship.name
|
||||
|
||||
# filter =
|
||||
# case relationship.cardinality do
|
||||
# :many ->
|
||||
# case value do
|
||||
# [single_identifier] ->
|
||||
# single_identifier
|
||||
|
||||
# many ->
|
||||
# [or: many]
|
||||
# end
|
||||
|
||||
# :one ->
|
||||
# value
|
||||
# end
|
||||
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: default_read.authorization_steps,
|
||||
# resource: relationship.destination,
|
||||
# action_type: :read,
|
||||
# filter: filter,
|
||||
# must_fetch?: false,
|
||||
# state_key: [:relationships, relationship_name, :to_relate],
|
||||
# fetcher: fn _ ->
|
||||
# case api.read(destination, filter: filter, paginate: false) do
|
||||
# {:ok, %{results: results}} -> {:ok, results}
|
||||
# {:error, error} -> {:error, error}
|
||||
# end
|
||||
# end,
|
||||
# relationship: [relationship.name],
|
||||
# source: "read prior to write related #{relationship.name}"
|
||||
# )
|
||||
# end
|
||||
|
||||
# defp identifier_or_identifiers(relationship, data) do
|
||||
# case relationship.cardinality do
|
||||
# :many ->
|
||||
# Ash.Actions.PrimaryKeyHelpers.values_to_primary_key_filters(
|
||||
# relationship.destination,
|
||||
# data
|
||||
# )
|
||||
|
||||
# :one ->
|
||||
# Ash.Actions.PrimaryKeyHelpers.value_to_primary_key_filter(
|
||||
# relationship.destination,
|
||||
# data
|
||||
# )
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp add_update_replace_authorizations(api, action, relationship, data, changeset) do
|
||||
# case identifier_or_identifiers(relationship, data) do
|
||||
# {:ok, identifiers} ->
|
||||
# read_currently_related_authorization =
|
||||
# read_currently_related_authorization(api, changeset, relationship)
|
||||
|
||||
# read_authorization =
|
||||
# read_related_authorization(api, relationship, identifiers, :to_replace)
|
||||
|
||||
# changeset
|
||||
# |> add_to_relationship_change_metadata(relationship, :replace, identifiers)
|
||||
# |> add_authorizations(read_currently_related_authorization)
|
||||
# |> add_authorizations(read_authorization)
|
||||
# |> changes_depend_on([:relationships, relationship.name, :replace_result])
|
||||
|
||||
# {:error, error} ->
|
||||
# {:error, error}
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp to_replace_relationship_authorization(api, %{source: resource} = relationship, identifiers) do
|
||||
# # TODO: This needs to have a state_key of `replace_result`, and this should look
|
||||
# # at the `current` state for the relationship, and return %{add: add, replace: replace}
|
||||
# # then in `authorization_changeset` we go through all relationships being replaced,
|
||||
# # and read their `replace_results` and put them into the authorization changeset.
|
||||
# # This also needs to make the required changes!
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: _read.authorization_steps,
|
||||
# resource: resource,
|
||||
# action_type: :update
|
||||
# )
|
||||
# end
|
||||
|
||||
defp add_relationship_currently_related_authorization(
|
||||
changeset,
|
||||
api,
|
||||
|
@ -507,13 +449,13 @@ defmodule Ash.Actions.Relationships do
|
|||
authorization =
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
authorization_steps: default_read.authorization_steps,
|
||||
rules: default_read.rules,
|
||||
resource: destination,
|
||||
action_type: :read,
|
||||
state_key: [:relationships, relationship.name, :current],
|
||||
must_fetch?: true,
|
||||
filter: filter,
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn _, _ ->
|
||||
case api.read(destination, filter: filter_statement) do
|
||||
{:ok, %{results: results}} -> {:ok, results}
|
||||
{:error, error} -> {:error, error}
|
||||
|
@ -529,61 +471,6 @@ defmodule Ash.Actions.Relationships do
|
|||
|> changes_depend_on([:relationships, relationship.name, :current])
|
||||
end
|
||||
|
||||
# defp add_create_authorizations(api, relationship, data, changeset) do
|
||||
# case identifier_or_identifiers(relationship, data) do
|
||||
# {:ok, identifiers} ->
|
||||
# read_authorization = read_related_authorization(api, relationship, identifiers, :to_add)
|
||||
|
||||
# changeset
|
||||
# |> add_to_relationship_change_metadata(relationship, :add, identifiers)
|
||||
# |> add_authorizations(read_authorization)
|
||||
# |> add_authorizations(add_authorization)
|
||||
# |> set_belongs_to_change(relationship, identifiers)
|
||||
|
||||
# {:error, error} ->
|
||||
# {:error, error}
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp add_to_relationship_change_metadata(changeset, relationship, key, identifiers) do
|
||||
# changeset
|
||||
# |> Map.put_new(:__ash_relationships__, %{})
|
||||
# |> Map.update!(:__ash_relationships__, fn ash_relationships ->
|
||||
# ash_relationships
|
||||
# |> Map.put_new(relationship.name, %{})
|
||||
# |> Map.update!(relationship.name, fn changes -> Map.put(changes, key, identifiers) end)
|
||||
# end)
|
||||
# end
|
||||
|
||||
# defp set_belongs_to_change(
|
||||
# changeset,
|
||||
# %{
|
||||
# type: :belongs_to,
|
||||
# source_field: source_field,
|
||||
# name: name,
|
||||
# destination_field: destination_field
|
||||
# },
|
||||
# value
|
||||
# ) do
|
||||
# if Keyword.has_key?(value, destination_field) do
|
||||
# changeset
|
||||
# |> Ecto.Changeset.put_change(
|
||||
# source_field,
|
||||
# Keyword.fetch!(value, destination_field)
|
||||
# )
|
||||
# |> Map.put_new(:__ash_skip_authorization_fields__, [])
|
||||
# |> Map.update!(:__ash_skip_authorization_fields__, fn fields ->
|
||||
# [source_field | fields]
|
||||
# end)
|
||||
# else
|
||||
# changes_depend_on(changeset, [:relationships, name, :to_add])
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp set_belongs_to_change(changeset, _, _) do
|
||||
# changeset
|
||||
# end
|
||||
|
||||
defp changes_depend_on(changeset, path) do
|
||||
Map.update(changeset, :__changes_depend_on__, [path], fn paths -> [path | paths] end)
|
||||
end
|
||||
|
@ -592,383 +479,4 @@ defmodule Ash.Actions.Relationships do
|
|||
authorizations = List.wrap(authorizations)
|
||||
Map.update(changeset, :__authorizations__, authorizations, &Kernel.++(&1, authorizations))
|
||||
end
|
||||
|
||||
# defp add_to_relationship_authorization(
|
||||
# api,
|
||||
# identifier,
|
||||
# %{type: :has_one, name: name, destination: destination} = relationship,
|
||||
# changeset
|
||||
# ) do
|
||||
# pkey = Ash.primary_key(destination)
|
||||
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: relationship.authorization_steps,
|
||||
# resource: relationship.source,
|
||||
# changeset: changeset,
|
||||
# action_type: :create,
|
||||
# state_key: :data,
|
||||
# must_fetch?: true,
|
||||
# dependencies: [:data, [:relationships, name, :to_add]],
|
||||
# is_fetched: fn data ->
|
||||
# case data do
|
||||
# %{^name => %Ecto.Association.NotLoaded{}} -> false
|
||||
# _ -> true
|
||||
# end
|
||||
# end,
|
||||
# fetcher: fn %{data: data, relationships: %{^name => %{:to_add => to_add}}} ->
|
||||
# pkey_value = Keyword.take(identifier, pkey)
|
||||
|
||||
# related =
|
||||
# Enum.find(to_add, fn to_relate ->
|
||||
# to_relate
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(pkey_value)
|
||||
# end)
|
||||
|
||||
# updated =
|
||||
# api.update(related,
|
||||
# attributes: %{
|
||||
# relationship.destination_field => Map.get(data, relationship.source_field)
|
||||
# }
|
||||
# )
|
||||
|
||||
# case updated do
|
||||
# {:ok, updated} -> {:ok, Map.put(data, relationship.name, updated)}
|
||||
# {:error, error} -> {:error, error}
|
||||
# end
|
||||
# end,
|
||||
# relationship: [],
|
||||
# bypass_strict_access?: true,
|
||||
# source: "Update relationship #{name}"
|
||||
# )
|
||||
# end
|
||||
|
||||
# defp add_to_relationship_authorization(
|
||||
# api,
|
||||
# _identifier,
|
||||
# %{type: :belongs_to, name: name} = relationship,
|
||||
# changeset
|
||||
# ) do
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: relationship.authorization_steps,
|
||||
# resource: relationship.source,
|
||||
# action_type: :update,
|
||||
# state_key: :data,
|
||||
# is_fetched: fn data ->
|
||||
# case data do
|
||||
# %{^name => %Ecto.Association.NotLoaded{}} -> false
|
||||
# _ -> true
|
||||
# end
|
||||
# end,
|
||||
# must_fetch?: true,
|
||||
# dependencies: [:data, [:relationships, name, :to_add]],
|
||||
# bypass_strict_access?: true,
|
||||
# changeset: changeset,
|
||||
# fetcher: fn %{data: data, relationships: %{^name => %{:to_add => to_add}}} ->
|
||||
# case to_add do
|
||||
# [item] -> {:ok, Map.put(data, name, item)}
|
||||
# _ -> raise "Internal relationship assumption failed."
|
||||
# end
|
||||
# end,
|
||||
# relationship: [],
|
||||
# source: "Set relationship #{relationship.name}"
|
||||
# )
|
||||
# end
|
||||
|
||||
# defp add_to_relationship_authorization(
|
||||
# api,
|
||||
# identifiers,
|
||||
# %{destination: destination, name: name, type: :many_to_many} = relationship,
|
||||
# changeset
|
||||
# ) do
|
||||
# Enum.flat_map(identifiers, fn identifier ->
|
||||
# pkey = Ash.primary_key(destination)
|
||||
|
||||
# default_create =
|
||||
# Ash.primary_action(relationship.through, :create) ||
|
||||
# raise "Must define a default create action for #{relationship.through}"
|
||||
|
||||
# [
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: default_create.authorization_steps,
|
||||
# resource: relationship.through,
|
||||
# changeset: fn %{data: data, relationships: %{^name => %{to_add: to_add}}} ->
|
||||
# pkey_value = Keyword.take(identifier, pkey)
|
||||
|
||||
# related =
|
||||
# Enum.find(to_add, fn to_relate ->
|
||||
# to_relate
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(pkey_value)
|
||||
# end)
|
||||
|
||||
# attributes = %{
|
||||
# relationship.destination_field_on_join_table =>
|
||||
# Map.fetch!(related, relationship.destination_field_on_join_table),
|
||||
# relationship.source_field_on_join_table =>
|
||||
# Map.fetch!(data, relationship.source_field_on_join_table)
|
||||
# }
|
||||
|
||||
# changeset = Ash.Actions.Create.changeset(api, relationship.through, attributes)
|
||||
|
||||
# if changeset.valid? do
|
||||
# {:ok, changeset}
|
||||
# else
|
||||
# {:error, changeset}
|
||||
# end
|
||||
# end,
|
||||
# action_type: :create,
|
||||
# state_key: [:relationships, name, :created_join_table_rows],
|
||||
# must_fetch?: true,
|
||||
# dependencies: [[:relationships, name, :to_add], :data],
|
||||
# fetcher: fn %{data: data, relationships: %{^name => %{to_add: to_add}}} ->
|
||||
# pkey_value = Keyword.take(identifier, pkey)
|
||||
|
||||
# related =
|
||||
# Enum.find(to_add, fn to_relate ->
|
||||
# to_relate
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(pkey_value)
|
||||
# end)
|
||||
|
||||
# attributes = %{
|
||||
# relationship.destination_field_on_join_table =>
|
||||
# Map.fetch!(related, relationship.destination_field),
|
||||
# relationship.source_field_on_join_table =>
|
||||
# Map.fetch!(data, relationship.source_field)
|
||||
# }
|
||||
|
||||
# api.create(relationship.through, attributes: attributes)
|
||||
# end,
|
||||
# relationship: [],
|
||||
# bypass_strict_access?: true,
|
||||
# source: "Create join entry for relationship #{name}"
|
||||
# ),
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: relationship.authorization_steps,
|
||||
# resource: relationship.source,
|
||||
# changeset: changeset,
|
||||
# action_type: :create,
|
||||
# state_key: :data,
|
||||
# must_fetch?: true,
|
||||
# dependencies: [[:relationships, name, :to_add], :data],
|
||||
# is_fetched: fn data ->
|
||||
# case Map.get(data, name) do
|
||||
# %Ecto.Association.NotLoaded{} ->
|
||||
# false
|
||||
|
||||
# related ->
|
||||
# Enum.any?(related, fn related ->
|
||||
# related
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(identifier)
|
||||
# end)
|
||||
# end
|
||||
# end,
|
||||
# fetcher: fn %{data: data, relationships: %{^name => %{to_add: to_add}}} ->
|
||||
# pkey_value = Keyword.take(identifier, pkey)
|
||||
|
||||
# related =
|
||||
# Enum.find(to_add, fn to_relate ->
|
||||
# to_relate
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(pkey_value)
|
||||
# end)
|
||||
|
||||
# data_with_related =
|
||||
# Map.update!(data, name, fn
|
||||
# %Ecto.Association.NotLoaded{} ->
|
||||
# [related]
|
||||
|
||||
# items ->
|
||||
# items ++ [related]
|
||||
# end)
|
||||
|
||||
# {:ok, data_with_related}
|
||||
# end,
|
||||
# relationship: [],
|
||||
# bypass_strict_access?: true,
|
||||
# source: "Update relationship #{name}"
|
||||
# )
|
||||
# ]
|
||||
# end)
|
||||
# end
|
||||
|
||||
# defp add_to_relationship_authorization(
|
||||
# api,
|
||||
# identifiers,
|
||||
# %{destination: destination, name: name, type: :has_many} = relationship,
|
||||
# changeset
|
||||
# ) do
|
||||
# Enum.flat_map(identifiers, fn identifier ->
|
||||
# pkey = Ash.primary_key(destination)
|
||||
|
||||
# [
|
||||
# Ash.Authorization.Request.new(
|
||||
# api: api,
|
||||
# authorization_steps: relationship.authorization_steps,
|
||||
# resource: relationship.source,
|
||||
# changeset: changeset,
|
||||
# action_type: :create,
|
||||
# state_key: :data,
|
||||
# must_fetch?: true,
|
||||
# dependencies: [[:relationships, name, :to_add], :data],
|
||||
# is_fetched: fn data ->
|
||||
# case Map.get(data, name) do
|
||||
# %Ecto.Association.NotLoaded{} ->
|
||||
# false
|
||||
|
||||
# related ->
|
||||
# Enum.any?(related, fn related ->
|
||||
# related
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(identifier)
|
||||
# end)
|
||||
# end
|
||||
# end,
|
||||
# fetcher: fn %{data: data, relationships: %{^name => %{to_add: to_add}}} ->
|
||||
# pkey_value = Keyword.take(identifier, pkey)
|
||||
|
||||
# related =
|
||||
# Enum.find(to_add, fn to_relate ->
|
||||
# to_relate
|
||||
# |> Map.take(pkey)
|
||||
# |> Map.to_list()
|
||||
# |> Kernel.==(pkey_value)
|
||||
# end)
|
||||
|
||||
# updated =
|
||||
# api.update(related,
|
||||
# attributes: %{
|
||||
# relationship.destination_field => Map.get(data, relationship.source_field)
|
||||
# }
|
||||
# )
|
||||
|
||||
# case updated do
|
||||
# {:ok, updated} ->
|
||||
# updated_with_related =
|
||||
# Map.update!(data, name, fn
|
||||
# %Ecto.Association.NotLoaded{} ->
|
||||
# [updated]
|
||||
|
||||
# items ->
|
||||
# items ++ [updated]
|
||||
# end)
|
||||
|
||||
# {:ok, updated_with_related}
|
||||
|
||||
# {:error, error} ->
|
||||
# {:error, error}
|
||||
# end
|
||||
# end,
|
||||
# relationship: [],
|
||||
# bypass_strict_access?: true,
|
||||
# source: "Update relationship #{name}"
|
||||
# )
|
||||
# ]
|
||||
# end)
|
||||
# end
|
||||
end
|
||||
|
||||
# def handle_update_relationships(changeset, api, relationships_input) do
|
||||
# Enum.reduce(relationships_input, changeset, fn {name, data}, changeset ->
|
||||
# case Ash.relationship(changeset.data.__struct__, name) do
|
||||
# nil ->
|
||||
# Ecto.Changeset.add_error(changeset, name, "Invalid relationship")
|
||||
|
||||
# relationship ->
|
||||
# cond do
|
||||
# !is_map(data) ->
|
||||
# add_update_replace_authorizations(api, relationship, data, changeset)
|
||||
|
||||
# keys_are?(data, [:add, :remove]) ->
|
||||
# raise "uh oh, not there yet! 1"
|
||||
|
||||
# # add needs to take an action type so we can authorize it properly
|
||||
|
||||
# # changeset = add_update_add_authorizations(api, relationship, data, changeset)
|
||||
# # add_update_remove_authorizations(api, relationships, data, changeset)
|
||||
# keys_are?(data, [:add]) ->
|
||||
# raise "uh oh, not there yet! 2"
|
||||
|
||||
# # add_update_add_authorizations(api, relationship, data, changeset)
|
||||
# keys_are?(data, [:remove]) ->
|
||||
# raise "uh oh, not there yet! 3"
|
||||
|
||||
# # add_update_remove_authorizations(api, relationships, data, changeset)
|
||||
# keys_are?(data, [:replace]) ->
|
||||
# add_update_replace_authorizations(api, relationship, data, changeset)
|
||||
|
||||
# keys_are?(data, [:add, :replace]) ->
|
||||
# Ecto.Changeset.add_error(
|
||||
# changeset,
|
||||
# relationship.name,
|
||||
# "Cannot add to a relationship and replace it at the same time."
|
||||
# )
|
||||
|
||||
# true ->
|
||||
# Ecto.Changeset.add_error(
|
||||
# changeset,
|
||||
# relationship.name,
|
||||
# "Invalid relationship data provided 1"
|
||||
# )
|
||||
# end
|
||||
# end
|
||||
# end)
|
||||
# end
|
||||
|
||||
# defp keys_are?(keyword, keys) do
|
||||
# Enum.sort(Map.keys(keyword)) == Enum.sort(keys)
|
||||
# end
|
||||
|
||||
# def handle_create_relationships(changeset, api, relationships_input) do
|
||||
# Enum.reduce(relationships_input, changeset, fn {name, data}, changeset ->
|
||||
# case Ash.relationship(changeset.data.__struct__, name) do
|
||||
# nil ->
|
||||
# Ecto.Changeset.add_error(changeset, name, "Invalid relationship")
|
||||
|
||||
# relationship ->
|
||||
# cond do
|
||||
# !is_map(data) ->
|
||||
# add_create_authorizations(api, relationship, data, changeset)
|
||||
|
||||
# keys_are?(data, [:add]) ->
|
||||
# add_create_authorizations(api, relationship, data[:add], changeset)
|
||||
|
||||
# keys_are?(data, [:replace]) ->
|
||||
# add_create_authorizations(api, relationship, data[:replace], changeset)
|
||||
|
||||
# keys_are?(data, [:add, :replace]) ->
|
||||
# Ecto.Changeset.add_error(
|
||||
# changeset,
|
||||
# relationship.name,
|
||||
# "Cannot add to a relationship and replace it at the same time."
|
||||
# )
|
||||
|
||||
# Keyword.has_key?(data, :remove) ->
|
||||
# Ecto.Changeset.add_error(
|
||||
# changeset,
|
||||
# relationship.name,
|
||||
# "Cannot remove from a relationship on create."
|
||||
# )
|
||||
|
||||
# true ->
|
||||
# Ecto.Changeset.add_error(
|
||||
# changeset,
|
||||
# relationship.name,
|
||||
# "Invalid relationship data provided 2"
|
||||
# )
|
||||
# end
|
||||
# end
|
||||
# end)
|
||||
# end
|
||||
|
|
|
@ -51,9 +51,9 @@ defmodule Ash.Actions.SideLoad do
|
|||
auth =
|
||||
Ash.Authorization.Request.new(
|
||||
action_type: :read,
|
||||
authorization_steps: default_read.authorization_steps,
|
||||
rules: default_read.rules,
|
||||
filter: filter,
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn _, _ ->
|
||||
case api.read(resource, filter: filter, paginate: false) do
|
||||
{:ok, %{results: results}} -> {:ok, results}
|
||||
{:error, error} -> {:error, error}
|
||||
|
|
|
@ -59,14 +59,14 @@ defmodule Ash.Actions.Update do
|
|||
update_authorization_request =
|
||||
Ash.Authorization.Request.new(
|
||||
api: api,
|
||||
authorization_steps: action.authorization_steps,
|
||||
rules: action.rules,
|
||||
changeset:
|
||||
Ash.Actions.Relationships.authorization_changeset(
|
||||
changeset,
|
||||
relationships
|
||||
),
|
||||
action_type: action.type,
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn changeset, _ ->
|
||||
Ash.DataLayer.update(resource, changeset)
|
||||
end,
|
||||
dependencies: Map.get(changeset, :__changes_depend_on__) || [],
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Ash.Authorization do
|
|||
@moduledoc """
|
||||
#TODO: Explain authorization
|
||||
|
||||
Authorization in Ash is done via declaring `authorization_steps` for actions,
|
||||
Authorization in Ash is done via declaring `rules` for actions,
|
||||
and in the case of stateful actions, via declaring `authoriation_steps` on attributes
|
||||
and relationships.
|
||||
|
||||
|
|
|
@ -22,32 +22,35 @@ defmodule Ash.Authorization.Authorizer do
|
|||
def authorize(user, requests, opts \\ []) do
|
||||
strict_access? = Keyword.get(opts, :strict_access?, true)
|
||||
|
||||
if opts[:fetch_only?] do
|
||||
fetch_must_fetch(requests, %{})
|
||||
else
|
||||
case Enum.find(requests, fn request -> Enum.empty?(request.authorization_steps) end) do
|
||||
nil ->
|
||||
{new_requests, facts} = strict_check_facts(user, requests, strict_access?)
|
||||
|
||||
solve(
|
||||
new_requests,
|
||||
user,
|
||||
facts,
|
||||
facts,
|
||||
%{user: user},
|
||||
strict_access?,
|
||||
opts[:log_final_report?] || false
|
||||
)
|
||||
|
||||
request ->
|
||||
exception = Ash.Error.Forbidden.exception(no_steps_configured: request)
|
||||
|
||||
if opts[:log_final_report?] do
|
||||
Logger.info(Ash.Error.Forbidden.report_text(exception))
|
||||
end
|
||||
|
||||
{:error, exception}
|
||||
requests =
|
||||
if opts[:fetch_only?] do
|
||||
Enum.map(requests, &Request.authorize_always/1)
|
||||
else
|
||||
requests
|
||||
end
|
||||
|
||||
case Enum.find(requests, fn request -> Enum.empty?(request.rules) end) do
|
||||
nil ->
|
||||
{new_requests, facts} = strict_check_facts(user, requests, strict_access?)
|
||||
|
||||
solve(
|
||||
new_requests,
|
||||
user,
|
||||
facts,
|
||||
facts,
|
||||
%{user: user},
|
||||
strict_access?,
|
||||
opts[:log_final_report?] || false
|
||||
)
|
||||
|
||||
request ->
|
||||
exception = Ash.Error.Forbidden.exception(no_steps_configured: request)
|
||||
|
||||
if opts[:log_final_report?] do
|
||||
Logger.info(Ash.Error.Forbidden.report_text(exception))
|
||||
end
|
||||
|
||||
{:error, exception}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -55,42 +58,63 @@ defmodule Ash.Authorization.Authorizer do
|
|||
requests,
|
||||
user,
|
||||
facts,
|
||||
strict_check_facts,
|
||||
initial_strict_check_facts,
|
||||
state,
|
||||
strict_access?,
|
||||
log_final_report?
|
||||
) do
|
||||
case sat_solver(requests, facts, [], state) do
|
||||
{:error, :unsatisfiable} ->
|
||||
exception =
|
||||
Ash.Error.Forbidden.exception(
|
||||
requests: requests,
|
||||
facts: facts,
|
||||
strict_check_facts: strict_check_facts,
|
||||
strict_access?: strict_access?,
|
||||
state: state
|
||||
)
|
||||
|
||||
if log_final_report? do
|
||||
Logger.info(Ash.Error.Forbidden.report_text(exception))
|
||||
requests_with_changeset =
|
||||
Enum.reduce_while(requests, {:ok, []}, fn request, {:ok, requests} ->
|
||||
if Request.dependencies_met?(state, request) do
|
||||
case Request.fetch_changeset(state, request) do
|
||||
{:ok, request} -> {:cont, {:ok, [request | requests]}}
|
||||
{:error, error} -> {:halt, {:error, error}}
|
||||
end
|
||||
else
|
||||
{:cont, {:ok, [request | requests]}}
|
||||
end
|
||||
end)
|
||||
|
||||
{:error, exception}
|
||||
case requests_with_changeset do
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
|
||||
{:ok, scenario} ->
|
||||
requests
|
||||
|> get_all_scenarios(scenario, facts, state)
|
||||
|> Enum.uniq()
|
||||
|> remove_irrelevant_clauses()
|
||||
|> verify_scenarios(
|
||||
user,
|
||||
requests,
|
||||
facts,
|
||||
strict_check_facts,
|
||||
state,
|
||||
strict_access?,
|
||||
log_final_report?
|
||||
)
|
||||
{:ok, requests_with_changeset} ->
|
||||
{new_requests, new_facts} =
|
||||
strict_check_facts(user, requests_with_changeset, strict_access?, facts)
|
||||
|
||||
case sat_solver(new_requests, new_facts, [], state) do
|
||||
{:error, :unsatisfiable} ->
|
||||
exception =
|
||||
Ash.Error.Forbidden.exception(
|
||||
requests: new_requests,
|
||||
facts: new_facts,
|
||||
strict_check_facts: initial_strict_check_facts,
|
||||
strict_access?: strict_access?,
|
||||
state: state
|
||||
)
|
||||
|
||||
if log_final_report? do
|
||||
Logger.info(Ash.Error.Forbidden.report_text(exception))
|
||||
end
|
||||
|
||||
{:error, exception}
|
||||
|
||||
{:ok, scenario} ->
|
||||
new_requests
|
||||
|> get_all_scenarios(scenario, new_facts, state)
|
||||
|> Enum.uniq()
|
||||
|> remove_irrelevant_clauses()
|
||||
|> verify_scenarios(
|
||||
user,
|
||||
new_requests,
|
||||
new_facts,
|
||||
initial_strict_check_facts,
|
||||
state,
|
||||
strict_access?,
|
||||
log_final_report?
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -353,8 +377,8 @@ defmodule Ash.Authorization.Authorizer do
|
|||
end)
|
||||
end
|
||||
|
||||
defp strict_check_facts(user, requests, strict_access?) do
|
||||
Enum.reduce(requests, {[], %{true: true, false: false}}, fn request, {requests, facts} ->
|
||||
defp strict_check_facts(user, requests, strict_access?, initial \\ %{true: true, false: false}) do
|
||||
Enum.reduce(requests, {[], initial}, fn request, {requests, facts} ->
|
||||
{new_request, new_facts} =
|
||||
Ash.Authorization.Checker.strict_check(user, request, facts, strict_access?)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule Ash.Authorization.Check.RelatingToUser do
|
|||
|
||||
def strict_check_relating?(pkey, pkey_value, changeset, opts) do
|
||||
case Map.fetch(changeset.__ash_relationships__, opts[:relationship_name]) do
|
||||
{:ok, %{add: relationship_change}} when is_list(relationship_change) ->
|
||||
{:ok, %{add: adding}} ->
|
||||
op =
|
||||
if opts[:allow_additional?] do
|
||||
:any?
|
||||
|
@ -29,26 +29,16 @@ defmodule Ash.Authorization.Check.RelatingToUser do
|
|||
:all?
|
||||
end
|
||||
|
||||
relationship_change =
|
||||
if Keyword.keyword?(relationship_change) do
|
||||
[relationship_change]
|
||||
else
|
||||
relationship_change
|
||||
end
|
||||
|
||||
found? =
|
||||
apply(Enum, op, [
|
||||
relationship_change,
|
||||
adding,
|
||||
fn relationship_change ->
|
||||
Keyword.take(relationship_change, pkey) == pkey_value
|
||||
Map.take(relationship_change, pkey) == Enum.into(pkey_value, %{})
|
||||
end
|
||||
])
|
||||
|
||||
found?
|
||||
|
||||
%{add: nil} ->
|
||||
false
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ defmodule Ash.Authorization.Checker do
|
|||
def strict_check(user, request, facts, strict_access?) do
|
||||
if Request.can_strict_check?(request) do
|
||||
new_facts =
|
||||
request.authorization_steps
|
||||
request.rules
|
||||
|> Enum.reduce(facts, fn {_step, clause}, facts ->
|
||||
case Map.fetch(facts, {request.relationship, clause}) do
|
||||
{:ok, _boolean_result} ->
|
||||
|
@ -52,9 +52,9 @@ defmodule Ash.Authorization.Checker do
|
|||
:all_scenarios_known
|
||||
|
||||
{[], _clauses_requiring_fetch} ->
|
||||
case fetch_requests(requests, state, facts, strict_access?, user) do
|
||||
{:ok, {new_requests, new_facts, new_state}} ->
|
||||
{:ok, new_requests, new_facts, new_state}
|
||||
case fetch_requests(requests, state, strict_access?) do
|
||||
{:ok, {new_requests, new_state}} ->
|
||||
{:ok, new_requests, facts, new_state}
|
||||
|
||||
:all_scenarios_known ->
|
||||
:all_scenarios_known
|
||||
|
@ -79,7 +79,7 @@ defmodule Ash.Authorization.Checker do
|
|||
end
|
||||
|
||||
# TODO: We could be smart here, and likely fetch multiple requests at a time
|
||||
defp fetch_requests(requests, state, facts, strict_access?, user) do
|
||||
defp fetch_requests(requests, state, strict_access?) do
|
||||
{fetchable_requests, other_requests} =
|
||||
Enum.split_with(requests, fn request ->
|
||||
bypass_strict? =
|
||||
|
@ -93,7 +93,7 @@ defmodule Ash.Authorization.Checker do
|
|||
Request.dependencies_met?(state, request)
|
||||
end)
|
||||
|
||||
requests_with_changesets =
|
||||
fetchable_requests_with_changeset =
|
||||
Enum.reduce_while(fetchable_requests, {:ok, []}, fn request, {:ok, requests} ->
|
||||
case Request.fetch_changeset(state, request) do
|
||||
{:ok, request} -> {:cont, {:ok, [request | requests]}}
|
||||
|
@ -101,39 +101,31 @@ defmodule Ash.Authorization.Checker do
|
|||
end
|
||||
end)
|
||||
|
||||
case requests_with_changesets do
|
||||
case fetchable_requests_with_changeset do
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
|
||||
{:ok, requests_with_changesets} ->
|
||||
requests_with_changesets
|
||||
{:ok, fetchable_requests_with_changeset} ->
|
||||
fetchable_requests_with_changeset
|
||||
|> Enum.sort_by(fn request ->
|
||||
# Requests that bypass strict access should generally perform well
|
||||
# as they would generally be more efficient checks
|
||||
{request.strict_check_completed?, Enum.count(request.relationship),
|
||||
not request.bypass_strict_access?, request.relationship}
|
||||
end)
|
||||
|> Enum.reduce({[], facts}, fn request, {requests, facts} ->
|
||||
{request, new_facts} = strict_check(user, request, facts, strict_access?)
|
||||
{[request | requests], new_facts}
|
||||
{Enum.count(request.relationship), not request.bypass_strict_access?,
|
||||
request.relationship}
|
||||
end)
|
||||
|> case do
|
||||
{[request | rest] = requests, new_facts} ->
|
||||
[request | rest] = requests ->
|
||||
case Request.fetch(state, request) do
|
||||
{:ok, new_state} ->
|
||||
new_requests = [%{request | is_fetched: true} | rest] ++ other_requests
|
||||
{:ok, {new_requests, new_facts, new_state}}
|
||||
{:ok, {new_requests, new_state}}
|
||||
|
||||
:error ->
|
||||
{:ok, {requests ++ other_requests, new_facts, state}}
|
||||
{:ok, {requests ++ other_requests, state}}
|
||||
end
|
||||
|
||||
{[], new_facts} ->
|
||||
if new_facts == facts do
|
||||
:all_scenarios_known
|
||||
else
|
||||
{:ok, {other_requests, new_facts, state}}
|
||||
end
|
||||
_ ->
|
||||
:all_scenarios_known
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -186,7 +178,7 @@ defmodule Ash.Authorization.Checker do
|
|||
Enum.split_with(clauses, fn clause ->
|
||||
Enum.any?(requests, fn request ->
|
||||
Request.fetched?(state, request) && Request.contains_clause?(request, clause) &&
|
||||
Request.dependencies_met?(state, request)
|
||||
Request.dependencies_met?(state, request) && Request.changeset_fetched?(request)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
@ -199,7 +191,7 @@ defmodule Ash.Authorization.Checker do
|
|||
|> Enum.map(&elem(&1, 0))
|
||||
end)
|
||||
|> Enum.reject(fn clause ->
|
||||
Map.has_key?(facts, clause)
|
||||
match?({:ok, _}, Ash.Authorization.Clause.find(facts, clause))
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,10 +11,19 @@ defmodule Ash.Authorization.Clause do
|
|||
}
|
||||
end
|
||||
|
||||
# TODO: Should we for sure special case this? I see no reason not to.
|
||||
def put_new_fact(facts, _rel, _resource, {Ash.Authorization.Clause.Static, _}, _) do
|
||||
facts
|
||||
end
|
||||
|
||||
def put_new_fact(facts, rel, resource, {mod, opts}, value, pkey \\ nil) do
|
||||
Map.put(facts, new(rel, resource, {mod, opts}, pkey), value)
|
||||
end
|
||||
|
||||
def find(_clauses, %{check_module: Ash.Authorization.Check.Static, check_opts: check_opts}) do
|
||||
{:ok, check_opts[:result]}
|
||||
end
|
||||
|
||||
def find(clauses, clause) do
|
||||
case Map.fetch(clauses, %{clause | pkey: nil}) do
|
||||
{:ok, value} ->
|
||||
|
|
|
@ -25,7 +25,12 @@ defmodule Ash.Authorization.Report do
|
|||
explained_steps =
|
||||
case report.state do
|
||||
%{data: data} when data not in [[], nil] ->
|
||||
explain_steps_with_data(report.requests, report.facts, data, report.strict_access?)
|
||||
explain_steps_with_data(
|
||||
report.requests,
|
||||
report.facts,
|
||||
List.wrap(data),
|
||||
report.strict_access?
|
||||
)
|
||||
|
||||
_ ->
|
||||
if report.strict_access? do
|
||||
|
@ -86,8 +91,8 @@ defmodule Ash.Authorization.Report do
|
|||
inner_title
|
||||
end
|
||||
|
||||
authorization_steps_legend =
|
||||
request.authorization_steps
|
||||
rules_legend =
|
||||
request.rules
|
||||
|> Enum.with_index()
|
||||
|> Enum.map_join("\n", fn {{step, check}, index} ->
|
||||
"#{index + 1}| " <>
|
||||
|
@ -109,10 +114,10 @@ defmodule Ash.Authorization.Report do
|
|||
end)
|
||||
|> add_header_line(indent("Record"))
|
||||
|> pad()
|
||||
|> add_step_info(request.authorization_steps, facts)
|
||||
|> add_step_info(request.rules, facts)
|
||||
|
||||
full_inner_title <>
|
||||
":\n" <> indent(authorization_steps_legend <> "\n\n" <> data_info <> "\n")
|
||||
":\n" <> indent(rules_legend <> "\n\n" <> data_info <> "\n")
|
||||
end)
|
||||
|
||||
title <> indent(contents)
|
||||
|
@ -281,7 +286,7 @@ defmodule Ash.Authorization.Report do
|
|||
end
|
||||
|
||||
contents =
|
||||
request.authorization_steps
|
||||
request.rules
|
||||
|> Enum.sort_by(fn {_step, clause} ->
|
||||
{Enum.count(clause.relationship), clause.relationship}
|
||||
end)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Ash.Authorization.Request do
|
||||
defstruct [
|
||||
:resource,
|
||||
:authorization_steps,
|
||||
:rules,
|
||||
:filter,
|
||||
:action_type,
|
||||
:dependencies,
|
||||
|
@ -20,7 +20,7 @@ defmodule Ash.Authorization.Request do
|
|||
@type t :: %__MODULE__{
|
||||
action_type: atom,
|
||||
resource: Ash.resource(),
|
||||
authorization_steps: list(term),
|
||||
rules: list(term),
|
||||
filter: Ash.Filter.t(),
|
||||
changeset: Ecto.Changeset.t(),
|
||||
dependencies: list(term),
|
||||
|
@ -40,12 +40,12 @@ defmodule Ash.Authorization.Request do
|
|||
opts =
|
||||
opts
|
||||
|> Keyword.put_new(:relationship, [])
|
||||
|> Keyword.put_new(:authorization_steps, [])
|
||||
|> Keyword.put_new(:rules, [])
|
||||
|> Keyword.put_new(:bypass_strict_access?, false)
|
||||
|> Keyword.put_new(:dependencies, [])
|
||||
|> Keyword.put_new(:strict_check_completed?, false)
|
||||
|> Keyword.put_new(:is_fetched, fn _ -> true end)
|
||||
|> Keyword.update!(:authorization_steps, fn steps ->
|
||||
|> Keyword.update!(:rules, fn steps ->
|
||||
Enum.map(steps, fn {step, fact} ->
|
||||
{step, Ash.Authorization.Clause.new(opts[:relationship] || [], opts[:resource], fact)}
|
||||
end)
|
||||
|
@ -54,8 +54,23 @@ defmodule Ash.Authorization.Request do
|
|||
struct!(__MODULE__, opts)
|
||||
end
|
||||
|
||||
def authorize_always(request) do
|
||||
%{
|
||||
request
|
||||
| rules: [
|
||||
authorize_if:
|
||||
Ash.Authorization.Clause.new(
|
||||
request.relationship,
|
||||
request.resource,
|
||||
{Ash.Authorization.Check.Static, result: true}
|
||||
)
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def can_strict_check?(%{changeset: changeset}) when is_function(changeset), do: false
|
||||
def can_strict_check?(_), do: true
|
||||
def can_strict_check?(%{strict_check_completed?: false}), do: true
|
||||
def can_strict_check?(_), do: false
|
||||
|
||||
def dependencies_met?(_state, %{dependencies: []}), do: true
|
||||
def dependencies_met?(_state, %{dependencies: nil}), do: true
|
||||
|
@ -70,7 +85,7 @@ defmodule Ash.Authorization.Request do
|
|||
end
|
||||
|
||||
def contains_clause?(request, clause) do
|
||||
Enum.any?(request.authorization_steps, fn {_step, request_clause} ->
|
||||
Enum.any?(request.rules, fn {_step, request_clause} ->
|
||||
clause == request_clause
|
||||
end)
|
||||
end
|
||||
|
@ -111,14 +126,17 @@ defmodule Ash.Authorization.Request do
|
|||
fetch_nested_value(state, key)
|
||||
end
|
||||
|
||||
def fetch(state, %{fetcher: fetcher, dependencies: dependencies} = request) do
|
||||
arg =
|
||||
def fetch(
|
||||
state,
|
||||
%{fetcher: fetcher, dependencies: dependencies, changeset: changeset} = request
|
||||
) do
|
||||
fetcher_state =
|
||||
Enum.reduce(dependencies, %{}, fn dependency, acc ->
|
||||
{:ok, value} = fetch_nested_value(state, dependency)
|
||||
put_nested_key(acc, dependency, value)
|
||||
end)
|
||||
|
||||
case fetcher.(arg) do
|
||||
case fetcher.(changeset, fetcher_state) do
|
||||
{:ok, value} ->
|
||||
{:ok, put_request_state(state, request, value)}
|
||||
|
||||
|
@ -127,6 +145,9 @@ defmodule Ash.Authorization.Request do
|
|||
end
|
||||
end
|
||||
|
||||
def changeset_fetched?(%{changeset: changeset}) when is_function(changeset), do: false
|
||||
def changeset_fetched?(%{changeset: _}), do: true
|
||||
|
||||
def fetch_changeset(state, %{dependencies: dependencies, changeset: changeset} = request)
|
||||
when is_function(changeset) do
|
||||
arg =
|
||||
|
@ -136,6 +157,9 @@ defmodule Ash.Authorization.Request do
|
|||
end)
|
||||
|
||||
case changeset.(arg) do
|
||||
%Ecto.Changeset{} = new_changeset ->
|
||||
{:ok, %{request | changeset: new_changeset}}
|
||||
|
||||
{:ok, new_changeset} ->
|
||||
{:ok, %{request | changeset: new_changeset}}
|
||||
|
||||
|
|
|
@ -3,18 +3,17 @@ defmodule Ash.Authorization.SatSolver do
|
|||
|
||||
def solve(requests, facts, negations, ids) when is_nil(ids) do
|
||||
requests
|
||||
|> Enum.map(&Map.get(&1, :authorization_steps))
|
||||
|> Enum.map(&Map.get(&1, :rules))
|
||||
|> build_requirements_expression(facts, nil)
|
||||
|> add_negations_and_solve(negations)
|
||||
end
|
||||
|
||||
def solve(requests, facts, negations, ids) do
|
||||
sets_of_authorization_steps = Enum.map(requests, &Map.get(&1, :authorization_steps))
|
||||
sets_of_rules = Enum.map(requests, &Map.get(&1, :rules))
|
||||
|
||||
ids
|
||||
|> Enum.reduce(nil, fn id, expr ->
|
||||
requirements_expression =
|
||||
build_requirements_expression(sets_of_authorization_steps, facts, id)
|
||||
requirements_expression = build_requirements_expression(sets_of_rules, facts, id)
|
||||
|
||||
if expr do
|
||||
{:and, expr, requirements_expression}
|
||||
|
@ -75,15 +74,15 @@ defmodule Ash.Authorization.SatSolver do
|
|||
end)
|
||||
end
|
||||
|
||||
defp build_requirements_expression(sets_of_authorization_steps, facts, pkey) do
|
||||
authorization_steps_expression =
|
||||
Enum.reduce(sets_of_authorization_steps, nil, fn authorization_steps, acc ->
|
||||
defp build_requirements_expression(sets_of_rules, facts, pkey) do
|
||||
rules_expression =
|
||||
Enum.reduce(sets_of_rules, nil, fn rules, acc ->
|
||||
case acc do
|
||||
nil ->
|
||||
compile_authorization_steps_expression(authorization_steps, facts, pkey)
|
||||
compile_rules_expression(rules, facts, pkey)
|
||||
|
||||
expr ->
|
||||
{:and, expr, compile_authorization_steps_expression(authorization_steps, facts, pkey)}
|
||||
{:and, expr, compile_rules_expression(rules, facts, pkey)}
|
||||
end
|
||||
end)
|
||||
|
||||
|
@ -99,9 +98,9 @@ defmodule Ash.Authorization.SatSolver do
|
|||
facts_expression = facts_to_statement(facts)
|
||||
|
||||
if facts_expression do
|
||||
{:and, facts_expression, authorization_steps_expression}
|
||||
{:and, facts_expression, rules_expression}
|
||||
else
|
||||
authorization_steps_expression
|
||||
rules_expression
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -118,7 +117,7 @@ defmodule Ash.Authorization.SatSolver do
|
|||
|
||||
defp solutions_to_predicate_values({:error, error}, _), do: {:error, error}
|
||||
|
||||
defp compile_authorization_steps_expression([{:authorize_if, clause}], facts, pkey) do
|
||||
defp compile_rules_expression([{:authorize_if, clause}], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
|
@ -130,7 +129,7 @@ defmodule Ash.Authorization.SatSolver do
|
|||
end
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:authorize_if, clause} | rest], facts, pkey) do
|
||||
defp compile_rules_expression([{:authorize_if, clause} | rest], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
|
@ -138,21 +137,20 @@ defmodule Ash.Authorization.SatSolver do
|
|||
true
|
||||
|
||||
{:ok, false} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
{:ok, :irrelevant} ->
|
||||
true
|
||||
|
||||
{:ok, :unknowable} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
:error ->
|
||||
{:or, Clause.expression(clause),
|
||||
compile_authorization_steps_expression(rest, facts, pkey)}
|
||||
{:or, Clause.expression(clause), compile_rules_expression(rest, facts, pkey)}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:authorize_unless, clause}], facts, pkey) do
|
||||
defp compile_rules_expression([{:authorize_unless, clause}], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
|
@ -173,12 +171,12 @@ defmodule Ash.Authorization.SatSolver do
|
|||
end
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:authorize_unless, clause} | rest], facts, pkey) do
|
||||
defp compile_rules_expression([{:authorize_unless, clause} | rest], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
{:ok, true} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
{:ok, false} ->
|
||||
true
|
||||
|
@ -187,19 +185,18 @@ defmodule Ash.Authorization.SatSolver do
|
|||
true
|
||||
|
||||
{:ok, :unknowable} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
:error ->
|
||||
{:or, {:not, Clause.expression(clause)},
|
||||
compile_authorization_steps_expression(rest, facts, pkey)}
|
||||
{:or, {:not, Clause.expression(clause)}, compile_rules_expression(rest, facts, pkey)}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:forbid_if, _clause}], _facts, _) do
|
||||
defp compile_rules_expression([{:forbid_if, _clause}], _facts, _) do
|
||||
false
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:forbid_if, clause} | rest], facts, pkey) do
|
||||
defp compile_rules_expression([{:forbid_if, clause} | rest], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
|
@ -207,30 +204,29 @@ defmodule Ash.Authorization.SatSolver do
|
|||
false
|
||||
|
||||
{:ok, :irrelevant} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
{:ok, :unknowable} ->
|
||||
false
|
||||
|
||||
{:ok, false} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
:error ->
|
||||
{:and, {:not, Clause.expression(clause)},
|
||||
compile_authorization_steps_expression(rest, facts, pkey)}
|
||||
{:and, {:not, Clause.expression(clause)}, compile_rules_expression(rest, facts, pkey)}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:forbid_unless, _clause}], _facts, _id) do
|
||||
defp compile_rules_expression([{:forbid_unless, _clause}], _facts, _id) do
|
||||
false
|
||||
end
|
||||
|
||||
defp compile_authorization_steps_expression([{:forbid_unless, clause} | rest], facts, pkey) do
|
||||
defp compile_rules_expression([{:forbid_unless, clause} | rest], facts, pkey) do
|
||||
clause = %{clause | pkey: pkey}
|
||||
|
||||
case Clause.find(facts, clause) do
|
||||
{:ok, true} ->
|
||||
compile_authorization_steps_expression(rest, facts, pkey)
|
||||
compile_rules_expression(rest, facts, pkey)
|
||||
|
||||
{:ok, false} ->
|
||||
false
|
||||
|
@ -242,8 +238,7 @@ defmodule Ash.Authorization.SatSolver do
|
|||
false
|
||||
|
||||
:error ->
|
||||
{:and, Clause.expression(clause),
|
||||
compile_authorization_steps_expression(rest, facts, pkey)}
|
||||
{:and, Clause.expression(clause), compile_rules_expression(rest, facts, pkey)}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -65,9 +65,9 @@ defmodule Ash.Filter do
|
|||
Ash.Authorization.Request.new(
|
||||
resource: resource,
|
||||
api: api,
|
||||
authorization_steps: Ash.primary_action(resource, :read).authorization_steps,
|
||||
rules: Ash.primary_action(resource, :read).rules,
|
||||
filter: parsed_filter,
|
||||
fetcher: fn _ ->
|
||||
fetcher: fn _, _ ->
|
||||
query = Ash.DataLayer.resource_to_query(resource)
|
||||
|
||||
case Ash.DataLayer.filter(query, parsed_filter, resource) do
|
||||
|
|
|
@ -128,7 +128,7 @@ defmodule Ash.Resource do
|
|||
Ash.Resource.Attributes.Attribute.new(mod, :id, :uuid,
|
||||
primary_key?: true,
|
||||
default: &Ecto.UUID.generate/0,
|
||||
authorization_steps: false
|
||||
write_rules: false
|
||||
)
|
||||
|
||||
Module.put_attribute(mod, :attributes, attribute)
|
||||
|
@ -140,7 +140,7 @@ defmodule Ash.Resource do
|
|||
{:ok, attribute} =
|
||||
Ash.Resource.Attributes.Attribute.new(mod, opts[:field], opts[:type],
|
||||
primary_key?: true,
|
||||
authorization_steps: false
|
||||
write_rules: false
|
||||
)
|
||||
|
||||
Module.put_attribute(mod, :attributes, attribute)
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
defmodule Ash.Resource.Actions.Create do
|
||||
@moduledoc "The representation of a `create` action."
|
||||
defstruct [:type, :name, :primary?, :authorization_steps]
|
||||
defstruct [:type, :name, :primary?, :rules]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :create,
|
||||
name: atom,
|
||||
primary?: boolean,
|
||||
authorization_steps: Authorizer.steps()
|
||||
rules: Authorizer.steps()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
opts: [
|
||||
primary?: :boolean,
|
||||
authorization_steps: :keyword
|
||||
rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
primary?: false,
|
||||
authorization_steps: []
|
||||
rules: []
|
||||
],
|
||||
describe: [
|
||||
primary?:
|
||||
"Whether or not this action should be used when no action is specified by the caller.",
|
||||
# TODO: doc better
|
||||
authorization_steps: "A list of authorization steps"
|
||||
rules: "A list of authorization steps"
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -33,8 +33,8 @@ defmodule Ash.Resource.Actions.Create do
|
|||
def new(resource, name, opts \\ []) do
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
rules =
|
||||
case opts[:rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -53,7 +53,7 @@ defmodule Ash.Resource.Actions.Create do
|
|||
name: name,
|
||||
type: :create,
|
||||
primary?: opts[:primary?],
|
||||
authorization_steps: authorization_steps
|
||||
rules: rules
|
||||
}}
|
||||
|
||||
{:error, error} ->
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
defmodule Ash.Resource.Actions.Destroy do
|
||||
@moduledoc "The representation of a `destroy` action"
|
||||
|
||||
defstruct [:type, :name, :primary?, :authorization_steps]
|
||||
defstruct [:type, :name, :primary?, :rules]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :destroy,
|
||||
name: atom,
|
||||
primary?: boolean,
|
||||
authorization_steps: Authorization.steps()
|
||||
rules: Authorization.steps()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
opts: [
|
||||
primary?: :boolean,
|
||||
authorization_steps: :keyword
|
||||
rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
primary?: false,
|
||||
authorization_steps: []
|
||||
rules: []
|
||||
],
|
||||
describe: [
|
||||
primary?:
|
||||
"Whether or not this action should be used when no action is specified by the caller.",
|
||||
# TODO: doc better
|
||||
authorization_steps: "A list of authorization steps"
|
||||
rules: "A list of authorization steps"
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -35,8 +35,8 @@ defmodule Ash.Resource.Actions.Destroy do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
rules =
|
||||
case opts[:rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -55,7 +55,7 @@ defmodule Ash.Resource.Actions.Destroy do
|
|||
name: name,
|
||||
type: :destroy,
|
||||
primary?: opts[:primary?],
|
||||
authorization_steps: authorization_steps
|
||||
rules: rules
|
||||
}}
|
||||
|
||||
{:error, error} ->
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
defmodule Ash.Resource.Actions.Read do
|
||||
@moduledoc "The representation of a `read` action"
|
||||
|
||||
defstruct [:type, :name, :primary?, :authorization_steps, :paginate?]
|
||||
defstruct [:type, :name, :primary?, :rules, :paginate?]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :read,
|
||||
name: atom,
|
||||
primary?: boolean,
|
||||
paginate?: boolean,
|
||||
authorization_steps: Authorization.steps()
|
||||
rules: Authorization.steps()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
opts: [
|
||||
primary?: :boolean,
|
||||
paginate?: :boolean,
|
||||
authorization_steps: :keyword
|
||||
rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
primary?: false,
|
||||
paginate?: true,
|
||||
authorization_steps: []
|
||||
rules: []
|
||||
],
|
||||
describe: [
|
||||
primary?:
|
||||
|
@ -28,7 +28,7 @@ defmodule Ash.Resource.Actions.Read do
|
|||
paginate?:
|
||||
"If false, a page is still returned from a read action, but no limit or offset is performed.",
|
||||
# TODO: doc better
|
||||
authorization_steps: "A list of authorization steps"
|
||||
rules: "A list of authorization steps"
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -40,8 +40,8 @@ defmodule Ash.Resource.Actions.Read do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
rules =
|
||||
case opts[:rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -60,7 +60,7 @@ defmodule Ash.Resource.Actions.Read do
|
|||
name: name,
|
||||
type: :read,
|
||||
primary?: opts[:primary?],
|
||||
authorization_steps: authorization_steps,
|
||||
rules: rules,
|
||||
paginate?: opts[:paginate?]
|
||||
}}
|
||||
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
defmodule Ash.Resource.Actions.Update do
|
||||
@moduledoc "The representation of a `update` action"
|
||||
|
||||
defstruct [:type, :name, :primary?, :authorization_steps]
|
||||
defstruct [:type, :name, :primary?, :rules]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :update,
|
||||
name: atom,
|
||||
primary?: boolean,
|
||||
authorization_steps: Authorization.steps()
|
||||
rules: Authorization.steps()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
opts: [
|
||||
primary?: :boolean,
|
||||
authorization_steps: :keyword
|
||||
rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
primary?: false,
|
||||
authorization_steps: []
|
||||
rules: []
|
||||
],
|
||||
describe: [
|
||||
primary?:
|
||||
"Whether or not this action should be used when no action is specified by the caller.",
|
||||
# TODO: doc better
|
||||
authorization_steps: "A list of authorization steps"
|
||||
rules: "A list of authorization steps"
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -35,8 +35,8 @@ defmodule Ash.Resource.Actions.Update do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
rules =
|
||||
case opts[:rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -55,7 +55,7 @@ defmodule Ash.Resource.Actions.Update do
|
|||
name: name,
|
||||
type: :update,
|
||||
primary?: opts[:primary?],
|
||||
authorization_steps: authorization_steps
|
||||
rules: rules
|
||||
}}
|
||||
|
||||
{:error, error} ->
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
defmodule Ash.Resource.Attributes.Attribute do
|
||||
@doc false
|
||||
|
||||
defstruct [:name, :type, :allow_nil?, :primary_key?, :default, :authorization_steps]
|
||||
defstruct [:name, :type, :allow_nil?, :primary_key?, :default, :write_rules]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
name: atom(),
|
||||
type: Ash.type(),
|
||||
primary_key?: boolean(),
|
||||
default: (() -> term),
|
||||
authorization_steps: Keyword.t()
|
||||
write_rules: Keyword.t()
|
||||
}
|
||||
|
||||
@schema Ashton.schema(
|
||||
opts: [
|
||||
primary_key?: :boolean,
|
||||
allow_nil?: :boolean,
|
||||
authorization_steps: [{:const, false}, :keyword],
|
||||
write_rules: [{:const, false}, :keyword],
|
||||
default: [
|
||||
{:function, 0},
|
||||
{:tuple, {:module, :atom}},
|
||||
|
@ -25,7 +25,7 @@ defmodule Ash.Resource.Attributes.Attribute do
|
|||
defaults: [
|
||||
primary_key?: false,
|
||||
allow_nil?: true,
|
||||
authorization_steps: []
|
||||
write_rules: []
|
||||
],
|
||||
describe: [
|
||||
allow_nil?: """
|
||||
|
@ -36,9 +36,9 @@ defmodule Ash.Resource.Attributes.Attribute do
|
|||
"Whether this field is, or is part of, the primary key of a resource.",
|
||||
default:
|
||||
"A one argument function that returns a default value, an mfa that does the same, or a raw value via specifying `{:constant, value}`.",
|
||||
authorization_steps: """
|
||||
Rules applied on an attribute during create or update. If no rules are defined, authorization to change will fail.
|
||||
If set to false, no rules are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
write_rules: """
|
||||
Write_Rules applied on an attribute during create or update. If no write_rules are defined, authorization to change will fail.
|
||||
If set to false, no write_rules are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
"""
|
||||
]
|
||||
)
|
||||
|
@ -51,8 +51,8 @@ defmodule Ash.Resource.Attributes.Attribute do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
with {:ok, opts} <- Ashton.validate(opts, @schema),
|
||||
{:default, {:ok, default}} <- {:default, cast_default(type, opts)} do
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
write_rules =
|
||||
case opts[:write_rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -72,7 +72,7 @@ defmodule Ash.Resource.Attributes.Attribute do
|
|||
%__MODULE__{
|
||||
name: name,
|
||||
type: type,
|
||||
authorization_steps: authorization_steps,
|
||||
write_rules: write_rules,
|
||||
allow_nil?: opts[:allow_nil?],
|
||||
primary_key?: opts[:primary_key?],
|
||||
default: default
|
||||
|
|
|
@ -12,7 +12,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
:source_field,
|
||||
:source,
|
||||
:reverse_relationship,
|
||||
:authorization_steps
|
||||
:write_rules
|
||||
]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
|
@ -26,7 +26,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
field_type: Ash.Type.t(),
|
||||
destination_field: atom,
|
||||
source_field: atom | nil,
|
||||
authorization_steps: Keyword.t()
|
||||
write_rules: Keyword.t()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
|
@ -37,14 +37,14 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
define_field?: :boolean,
|
||||
field_type: :atom,
|
||||
reverse_relationship: :atom,
|
||||
authorization_steps: :keyword
|
||||
write_rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
destination_field: :id,
|
||||
primary_key?: false,
|
||||
define_field?: true,
|
||||
field_type: :uuid,
|
||||
authorization_steps: []
|
||||
write_rules: []
|
||||
],
|
||||
describe: [
|
||||
reverse_relationship:
|
||||
|
@ -58,7 +58,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
"The field on this resource that should match the `destination_field` on the related resource. Default: [relationship_name]_id",
|
||||
primary_key?:
|
||||
"Whether this field is, or is part of, the primary key of a resource.",
|
||||
authorization_steps: """
|
||||
write_rules: """
|
||||
Steps applied on an relationship during create or update. If no steps are defined, authorization to change will fail.
|
||||
If set to false, no steps are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
Remember that any changes against the destination records *will* still be authorized regardless of this setting.
|
||||
|
@ -79,8 +79,8 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
write_rules =
|
||||
case opts[:write_rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -99,7 +99,7 @@ defmodule Ash.Resource.Relationships.BelongsTo do
|
|||
{:ok,
|
||||
%__MODULE__{
|
||||
name: name,
|
||||
authorization_steps: authorization_steps,
|
||||
write_rules: write_rules,
|
||||
source: resource,
|
||||
type: :belongs_to,
|
||||
cardinality: :one,
|
||||
|
|
|
@ -6,17 +6,17 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
:destination,
|
||||
:destination_field,
|
||||
:source_field,
|
||||
:authorization_steps,
|
||||
:write_rules,
|
||||
:source,
|
||||
:reverse_relationship,
|
||||
:authorization_steps
|
||||
:write_rules
|
||||
]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
type: :has_many,
|
||||
cardinality: :many,
|
||||
source: Ash.resource(),
|
||||
authorization_steps: Keyword.t(),
|
||||
write_rules: Keyword.t(),
|
||||
name: atom,
|
||||
type: Ash.Type.t(),
|
||||
destination: Ash.resource(),
|
||||
|
@ -29,12 +29,12 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
opts: [
|
||||
destination_field: :atom,
|
||||
source_field: :atom,
|
||||
authorization_steps: :keyword,
|
||||
write_rules: :keyword,
|
||||
reverse_relationship: :atom
|
||||
],
|
||||
defaults: [
|
||||
source_field: :id,
|
||||
authorization_steps: []
|
||||
write_rules: []
|
||||
],
|
||||
describe: [
|
||||
reverse_relationship:
|
||||
|
@ -43,7 +43,7 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
"The field on the related resource that should match the `source_field` on this resource. Default: [resource.name]_id",
|
||||
source_field:
|
||||
"The field on this resource that should match the `destination_field` on the related resource.",
|
||||
authorization_steps: """
|
||||
write_rules: """
|
||||
Steps applied on an relationship during create or update. If no steps are defined, authorization to change will fail.
|
||||
If set to false, no steps are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
Remember that any changes against the destination records *will* still be authorized regardless of this setting.
|
||||
|
@ -64,8 +64,8 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
write_rules =
|
||||
case opts[:write_rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -84,7 +84,7 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
{:ok,
|
||||
%__MODULE__{
|
||||
name: name,
|
||||
authorization_steps: authorization_steps,
|
||||
write_rules: write_rules,
|
||||
source: resource,
|
||||
type: :has_many,
|
||||
cardinality: :many,
|
||||
|
|
|
@ -9,7 +9,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
:destination_field,
|
||||
:source_field,
|
||||
:reverse_relationship,
|
||||
:authorization_steps,
|
||||
:write_rules,
|
||||
:allow_orphans?
|
||||
]
|
||||
|
||||
|
@ -19,7 +19,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
source: Ash.resource(),
|
||||
name: atom,
|
||||
type: Ash.Type.t(),
|
||||
authorization_steps: Keyword.t(),
|
||||
write_rules: Keyword.t(),
|
||||
destination: Ash.resource(),
|
||||
destination_field: atom,
|
||||
source_field: atom,
|
||||
|
@ -32,12 +32,12 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
destination_field: :atom,
|
||||
source_field: :atom,
|
||||
reverse_relationship: :atom,
|
||||
authorization_steps: :keyword,
|
||||
write_rules: :keyword,
|
||||
allow_orphans?: :boolean
|
||||
],
|
||||
defaults: [
|
||||
source_field: :id,
|
||||
authorization_steps: [],
|
||||
write_rules: [],
|
||||
# TODO: When we add constraint expressions, we should validate this with that.
|
||||
allow_orphans?: true
|
||||
],
|
||||
|
@ -51,7 +51,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
# TODO: Explain this better
|
||||
allow_orphans:
|
||||
"Whether or not to allow orphaned records that would result in replaced relationships.",
|
||||
authorization_steps: """
|
||||
write_rules: """
|
||||
Steps applied on an relationship during create or update. If no steps are defined, authorization to change will fail.
|
||||
If set to false, no steps are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
Remember that any changes against the destination records *will* still be authorized regardless of this setting.
|
||||
|
@ -74,8 +74,8 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
write_rules =
|
||||
case opts[:write_rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -101,7 +101,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
destination_field: opts[:destination_field] || :"#{resource_type}_id",
|
||||
source_field: opts[:source_field],
|
||||
reverse_relationship: opts[:reverse_relationship],
|
||||
authorization_steps: authorization_steps
|
||||
write_rules: write_rules
|
||||
}}
|
||||
|
||||
{:error, errors} ->
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
:source_field_on_join_table,
|
||||
:destination_field_on_join_table,
|
||||
:reverse_relationship,
|
||||
:authorization_steps
|
||||
:write_rules
|
||||
]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
|
@ -26,7 +26,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
source_field_on_join_table: atom,
|
||||
destination_field_on_join_table: atom,
|
||||
reverse_relationship: atom,
|
||||
authorization_steps: Keyword.t()
|
||||
write_rules: Keyword.t()
|
||||
}
|
||||
|
||||
@opt_schema Ashton.schema(
|
||||
|
@ -35,15 +35,15 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
destination_field_on_join_table: :atom,
|
||||
source_field: :atom,
|
||||
destination_field: :atom,
|
||||
authorization_steps: :keyword,
|
||||
write_rules: :keyword,
|
||||
through: :atom,
|
||||
reverse_relationship: :atom,
|
||||
authorization_steps: :keyword
|
||||
write_rules: :keyword
|
||||
],
|
||||
defaults: [
|
||||
source_field: :id,
|
||||
destination_field: :id,
|
||||
authorization_steps: []
|
||||
write_rules: []
|
||||
],
|
||||
required: [
|
||||
:through
|
||||
|
@ -60,7 +60,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
"The field on this resource that should line up with `source_field_on_join_table` on the join table.",
|
||||
destination_field:
|
||||
"The field on the related resource that should line up with `destination_field_on_join_table` on the join table.",
|
||||
authorization_steps: """
|
||||
write_rules: """
|
||||
Steps applied on an relationship during create or update. If no steps are defined, authorization to change will fail.
|
||||
If set to false, no steps are applied and any changes are allowed (assuming the action was authorized as a whole)
|
||||
Remember that any changes against the destination records *will* still be authorized regardless of this setting.
|
||||
|
@ -82,8 +82,8 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
# Don't call functions on the resource! We don't want it to compile here
|
||||
case Ashton.validate(opts, @opt_schema) do
|
||||
{:ok, opts} ->
|
||||
authorization_steps =
|
||||
case opts[:authorization_steps] do
|
||||
write_rules =
|
||||
case opts[:write_rules] do
|
||||
false ->
|
||||
false
|
||||
|
||||
|
@ -110,7 +110,7 @@ defmodule Ash.Resource.Relationships.ManyToMany do
|
|||
reverse_relationship: opts[:reverse_relationship],
|
||||
source_field: opts[:source_field],
|
||||
destination_field: opts[:destination_field],
|
||||
authorization_steps: authorization_steps,
|
||||
write_rules: write_rules,
|
||||
source_field_on_join_table:
|
||||
opts[:source_field_on_join_table] || :"#{resource_name}_id",
|
||||
destination_field_on_join_table:
|
||||
|
|
|
@ -7,24 +7,28 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: always()
|
||||
]
|
||||
|
||||
create :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
forbid_unless: setting_relationship(:author),
|
||||
authorize_if: user_attribute(:author, true)
|
||||
]
|
||||
end
|
||||
|
||||
# Change rules to `rules`
|
||||
# for attributes/relationship change them to `write_rules`
|
||||
attributes do
|
||||
attribute :contents, :string, authorization_steps: false
|
||||
attribute :contents, :string, write_rules: false
|
||||
attribute :color, :string, write_rules: false
|
||||
attribute :size, :string, write_rules: false
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :author, Ash.Test.Authorization.CreateAuthorizationTest.Author,
|
||||
authorization_steps: [
|
||||
write_rules: [
|
||||
authorize_if: relating_to_user()
|
||||
]
|
||||
end
|
||||
|
@ -35,20 +39,20 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
use Ash.DataLayer.Ets, private?: true
|
||||
|
||||
actions do
|
||||
read :default, authorization_steps: [authorize_if: always()]
|
||||
read :default, rules: [authorize_if: always()]
|
||||
|
||||
create :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: user_attribute(:admin, true),
|
||||
authorize_if: user_attribute(:manager, true)
|
||||
]
|
||||
end
|
||||
|
||||
attributes do
|
||||
attribute :name, :string, authorization_steps: false
|
||||
attribute :name, :string, write_rules: false
|
||||
|
||||
attribute :state, :string,
|
||||
authorization_steps: [
|
||||
write_rules: [
|
||||
authorize_if: user_attribute(:admin, true),
|
||||
forbid_if: setting(to: "closed"),
|
||||
authorize_if: always()
|
||||
|
@ -56,11 +60,11 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
|
||||
attribute :bio_locked, :boolean,
|
||||
default: {:constant, false},
|
||||
authorization_steps: false
|
||||
write_rules: false
|
||||
|
||||
attribute :self_manager, :boolean, authorization_steps: false
|
||||
attribute :self_manager, :boolean, write_rules: false
|
||||
|
||||
attribute :fired, :boolean, authorization_steps: false
|
||||
attribute :fired, :boolean, write_rules: false
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
@ -68,7 +72,7 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
through: Ash.Test.Authorization.CreateAuthorizationTest.AuthorPost
|
||||
|
||||
has_one :bio, Ash.Test.Authorization.CreateAuthorizationTest.Bio,
|
||||
authorization_steps: [
|
||||
write_rules: [
|
||||
forbid_if: attribute_equals(:bio_locked, true),
|
||||
authorize_if: always()
|
||||
]
|
||||
|
@ -81,18 +85,18 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: always()
|
||||
]
|
||||
|
||||
create :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
forbid_unless: setting_relationship(:author),
|
||||
authorize_if: user_attribute(:author, true)
|
||||
]
|
||||
|
||||
update :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: always()
|
||||
]
|
||||
end
|
||||
|
@ -100,14 +104,14 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
attributes do
|
||||
attribute :admin_only?, :boolean,
|
||||
default: {:constant, false},
|
||||
authorization_steps: [
|
||||
write_rules: [
|
||||
authorize_if: always()
|
||||
]
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :author, Author,
|
||||
authorization_steps: [
|
||||
write_rules: [
|
||||
authorize_if: relating_to_user()
|
||||
]
|
||||
end
|
||||
|
@ -157,18 +161,18 @@ defmodule Ash.Test.Authorization.CreateAuthorizationTest do
|
|||
read :default
|
||||
|
||||
create :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: user_attribute(:admin, true),
|
||||
authorize_if: user_attribute(:manager, true)
|
||||
]
|
||||
end
|
||||
|
||||
attributes do
|
||||
attribute :title, :string, authorization_steps: false
|
||||
attribute :title, :string, write_rules: false
|
||||
|
||||
attribute :contents, :string, authorization_steps: false
|
||||
attribute :contents, :string, write_rules: false
|
||||
|
||||
attribute :published, :boolean, authorization_steps: false
|
||||
attribute :published, :boolean, write_rules: false
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Ash.Test.Authorization.GetAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
# You can see yourself
|
||||
authorize_if: user_attribute_matches_record(:id, :id),
|
||||
# You can't see anything else unless you're a manager
|
||||
|
@ -75,7 +75,7 @@ defmodule Ash.Test.Authorization.GetAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: attribute_equals(:published, true),
|
||||
authorize_if: related_to_user_via(:authors)
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Ash.Test.Authorization.ReadAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
# You can see yourself
|
||||
authorize_if: user_attribute_matches_record(:id, :id),
|
||||
# You can't see anything else unless you're a manager
|
||||
|
@ -73,7 +73,7 @@ defmodule Ash.Test.Authorization.ReadAuthorizationTest do
|
|||
|
||||
actions do
|
||||
read :default,
|
||||
authorization_steps: [
|
||||
rules: [
|
||||
authorize_if: attribute_equals(:published, true),
|
||||
authorize_if: related_to_user_via(:authors)
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Ash.Test.Dsl.Resource.Actions.CreateTest do
|
|||
%Ash.Resource.Actions.Create{
|
||||
name: :default,
|
||||
primary?: true,
|
||||
authorization_steps: [],
|
||||
rules: [],
|
||||
type: :create
|
||||
}
|
||||
] = Ash.actions(Post)
|
||||
|
@ -62,11 +62,11 @@ defmodule Ash.Test.Dsl.Resource.Actions.CreateTest do
|
|||
test "it fails if `rules` is not a list" do
|
||||
assert_raise(
|
||||
Ash.Error.ResourceDslError,
|
||||
"option authorization_steps at actions -> create -> default must be keyword",
|
||||
"option rules at actions -> create -> default must be keyword",
|
||||
fn ->
|
||||
defposts do
|
||||
actions do
|
||||
create :default, authorization_steps: 10
|
||||
create :default, rules: 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Ash.Test.Dsl.Resource.Actions.DestroyTest do
|
|||
%Ash.Resource.Actions.Destroy{
|
||||
name: :default,
|
||||
primary?: true,
|
||||
authorization_steps: [],
|
||||
rules: [],
|
||||
type: :destroy
|
||||
}
|
||||
] = Ash.actions(Post)
|
||||
|
@ -62,11 +62,11 @@ defmodule Ash.Test.Dsl.Resource.Actions.DestroyTest do
|
|||
test "it fails if `rules` is not a list" do
|
||||
assert_raise(
|
||||
Ash.Error.ResourceDslError,
|
||||
"option authorization_steps at actions -> destroy -> default must be keyword",
|
||||
"option rules at actions -> destroy -> default must be keyword",
|
||||
fn ->
|
||||
defposts do
|
||||
actions do
|
||||
destroy :default, authorization_steps: 10
|
||||
destroy :default, rules: 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Ash.Test.Dsl.Resource.Actions.ReadTest do
|
|||
%Ash.Resource.Actions.Read{
|
||||
name: :default,
|
||||
primary?: true,
|
||||
authorization_steps: [],
|
||||
rules: [],
|
||||
type: :read
|
||||
}
|
||||
] = Ash.actions(Post)
|
||||
|
@ -62,11 +62,11 @@ defmodule Ash.Test.Dsl.Resource.Actions.ReadTest do
|
|||
test "it fails if `rules` is not a list" do
|
||||
assert_raise(
|
||||
Ash.Error.ResourceDslError,
|
||||
"option authorization_steps at actions -> read -> default must be keyword",
|
||||
"option rules at actions -> read -> default must be keyword",
|
||||
fn ->
|
||||
defposts do
|
||||
actions do
|
||||
read :default, authorization_steps: 10
|
||||
read :default, rules: 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Ash.Test.Dsl.Resource.Actions.UpdateTest do
|
|||
%Ash.Resource.Actions.Update{
|
||||
name: :default,
|
||||
primary?: true,
|
||||
authorization_steps: [],
|
||||
rules: [],
|
||||
type: :update
|
||||
}
|
||||
] = Ash.actions(Post)
|
||||
|
@ -62,11 +62,11 @@ defmodule Ash.Test.Dsl.Resource.Actions.UpdateTest do
|
|||
test "it fails if `rules` is not a list" do
|
||||
assert_raise(
|
||||
Ash.Error.ResourceDslError,
|
||||
"option authorization_steps at actions -> update -> default must be keyword",
|
||||
"option rules at actions -> update -> default must be keyword",
|
||||
fn ->
|
||||
defposts do
|
||||
actions do
|
||||
update :default, authorization_steps: 10
|
||||
update :default, rules: 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue