mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
fix: fix pattern match error on certain policy conditions
improvement: various policy behavior cleanups
This commit is contained in:
parent
b428fb8995
commit
2cd6360379
5 changed files with 45 additions and 48 deletions
|
@ -489,6 +489,10 @@ defmodule Ash.Policy.Authorizer do
|
||||||
log_successful_policy_breakdown(authorizer, filter)
|
log_successful_policy_breakdown(authorizer, filter)
|
||||||
{:filter, strict_check_all_facts(authorizer), filter}
|
{:filter, strict_check_all_facts(authorizer), filter}
|
||||||
|
|
||||||
|
{:filter_and_continue, filter, authorizer} ->
|
||||||
|
log_successful_policy_breakdown(authorizer, filter)
|
||||||
|
{:filter, strict_check_all_facts(authorizer), filter}
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
{:error, error}
|
{:error, error}
|
||||||
|
|
||||||
|
@ -959,7 +963,7 @@ defmodule Ash.Policy.Authorizer do
|
||||||
match?(
|
match?(
|
||||||
{:ok, _},
|
{:ok, _},
|
||||||
Ash.Policy.Policy.fetch_fact(authorizer.facts, {check_module, opts})
|
Ash.Policy.Policy.fetch_fact(authorizer.facts, {check_module, opts})
|
||||||
) || check_module.type() == :filter
|
)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -86,8 +86,8 @@ defmodule Ash.Policy.Checker do
|
||||||
|
|
||||||
{:ok, scenarios, authorizer} ->
|
{:ok, scenarios, authorizer} ->
|
||||||
scenarios
|
scenarios
|
||||||
|> Ash.Policy.SatSolver.simplify_clauses()
|
|
||||||
|> remove_scenarios_with_impossible_facts(authorizer)
|
|> remove_scenarios_with_impossible_facts(authorizer)
|
||||||
|
|> Ash.Policy.SatSolver.simplify_clauses()
|
||||||
|> case do
|
|> case do
|
||||||
[] -> {:ok, false, authorizer}
|
[] -> {:ok, false, authorizer}
|
||||||
scenarios -> {:ok, scenarios, authorizer}
|
scenarios -> {:ok, scenarios, authorizer}
|
||||||
|
@ -99,16 +99,18 @@ defmodule Ash.Policy.Checker do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp remove_scenarios_with_impossible_facts(scenarios, authorizer) do
|
defp remove_scenarios_with_impossible_facts(scenarios, authorizer) do
|
||||||
# Remove any scenarios with a fact that must be a certain value, but are not, at strict check time
|
|
||||||
# They aren't true, so that scenario isn't possible
|
|
||||||
|
|
||||||
Enum.reject(scenarios, fn scenario ->
|
Enum.reject(scenarios, fn scenario ->
|
||||||
Enum.any?(scenario, fn {{mod, opts}, required_value} ->
|
Enum.any?(scenario, fn {{mod, opts}, required_value} ->
|
||||||
opts[:access_type] == :strict &&
|
case Policy.fetch_fact(authorizer.facts, {mod, opts}) do
|
||||||
not match?(
|
{:ok, :unknown} ->
|
||||||
{:ok, ^required_value},
|
opts[:access_type] == :strict
|
||||||
Policy.fetch_fact(authorizer.facts, {mod, opts})
|
|
||||||
)
|
{:ok, value} ->
|
||||||
|
value != required_value
|
||||||
|
|
||||||
|
:error ->
|
||||||
|
opts[:access_type] == :strict
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
@ -443,13 +443,13 @@ defmodule Ash.Policy.Policy do
|
||||||
{condition_and_policy_expression, authorizer} =
|
{condition_and_policy_expression, authorizer} =
|
||||||
case compile_policy_expression(policies, authorizer) do
|
case compile_policy_expression(policies, authorizer) do
|
||||||
{true, authorizer} ->
|
{true, authorizer} ->
|
||||||
{condition, authorizer}
|
{condition_expression, authorizer}
|
||||||
|
|
||||||
{false, authorizer} ->
|
{false, authorizer} ->
|
||||||
{false, authorizer}
|
{false, authorizer}
|
||||||
|
|
||||||
{policy_expression, authorizer} ->
|
{policy_expression, authorizer} ->
|
||||||
{{:and, condition, policy_expression}, authorizer}
|
{{:and, condition_expression, policy_expression}, authorizer}
|
||||||
end
|
end
|
||||||
|
|
||||||
case condition_and_policy_expression do
|
case condition_and_policy_expression do
|
||||||
|
|
|
@ -51,36 +51,33 @@ defmodule Ash.Policy.SatSolver do
|
||||||
def simplify_clauses([scenario]), do: [scenario]
|
def simplify_clauses([scenario]), do: [scenario]
|
||||||
|
|
||||||
def simplify_clauses(scenarios) do
|
def simplify_clauses(scenarios) do
|
||||||
unnecessary_clauses =
|
indexed = Enum.with_index(scenarios)
|
||||||
scenarios
|
|
||||||
|> Enum.with_index()
|
|
||||||
|> Enum.flat_map(fn {scenario, index} ->
|
|
||||||
scenario
|
|
||||||
|> Enum.flat_map(fn {fact, _value} ->
|
|
||||||
if Enum.find(scenarios, fn other_scenario ->
|
|
||||||
scenario_makes_fact_irrelevant?(other_scenario, scenario, fact)
|
|
||||||
end) do
|
|
||||||
[fact]
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Enum.map(fn fact ->
|
|
||||||
{index, fact}
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|
|
||||||
|
|
||||||
case unnecessary_clauses do
|
indexed
|
||||||
empty when empty == %{} ->
|
|> Enum.find_value(fn {scenario, index} ->
|
||||||
|
Enum.find_value(scenario, fn {fact, _value} ->
|
||||||
|
case Enum.find_value(indexed, fn {other_scenario, other_index} ->
|
||||||
|
if scenario != other_scenario &&
|
||||||
|
scenario_makes_fact_irrelevant?(other_scenario, scenario, fact) do
|
||||||
|
other_index
|
||||||
|
end
|
||||||
|
end) do
|
||||||
|
nil ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
other_index ->
|
||||||
|
{fact, other_index, index}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
nil ->
|
||||||
scenarios
|
scenarios
|
||||||
|
|
||||||
unnecessary_clauses ->
|
{fact, index1, index2} ->
|
||||||
unnecessary_clauses
|
scenarios
|
||||||
|> Enum.reduce(scenarios, fn {index, facts}, scenarios ->
|
|> List.update_at(index1, &Map.delete(&1, fact))
|
||||||
List.update_at(scenarios, index, &Map.drop(&1, facts))
|
|> List.update_at(index2, &Map.delete(&1, fact))
|
||||||
end)
|
|
||||||
|> Enum.reject(&(&1 == %{}))
|
|
||||||
|> Enum.uniq()
|
|> Enum.uniq()
|
||||||
|> simplify_clauses()
|
|> simplify_clauses()
|
||||||
end
|
end
|
||||||
|
@ -91,18 +88,12 @@ defmodule Ash.Policy.SatSolver do
|
||||||
do: false
|
do: false
|
||||||
|
|
||||||
def scenario_makes_fact_irrelevant?(potential_irrelevant_maker, scenario, fact) do
|
def scenario_makes_fact_irrelevant?(potential_irrelevant_maker, scenario, fact) do
|
||||||
scenario_is_subset?(Map.delete(potential_irrelevant_maker, fact), scenario) &&
|
Map.delete(potential_irrelevant_maker, fact) == Map.delete(scenario, fact) &&
|
||||||
Map.has_key?(potential_irrelevant_maker, fact) && Map.has_key?(scenario, fact) &&
|
Map.has_key?(potential_irrelevant_maker, fact) && Map.has_key?(scenario, fact) &&
|
||||||
Map.get(potential_irrelevant_maker, fact) !=
|
Map.get(potential_irrelevant_maker, fact) !=
|
||||||
Map.get(scenario, fact)
|
Map.get(scenario, fact)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp scenario_is_subset?(left, right) do
|
|
||||||
Enum.all?(left, fn {fact, value} ->
|
|
||||||
Map.get(right, fact) == value
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec add_negations_and_solve(term, term) :: term | no_return()
|
@spec add_negations_and_solve(term, term) :: term | no_return()
|
||||||
defp add_negations_and_solve(cnf, negations) do
|
defp add_negations_and_solve(cnf, negations) do
|
||||||
solve_expression(cnf ++ negations)
|
solve_expression(cnf ++ negations)
|
||||||
|
|
|
@ -11,13 +11,13 @@ defmodule Ash.Test.Support.PolicyComplex.Post do
|
||||||
authorize_if always()
|
authorize_if always()
|
||||||
end
|
end
|
||||||
|
|
||||||
policy action_type(:read) do
|
policy [action_type(:read)] do
|
||||||
authorize_if relates_to_actor_via(:author)
|
authorize_if relates_to_actor_via(:author)
|
||||||
authorize_if relates_to_actor_via([:author, :friends])
|
authorize_if relates_to_actor_via([:author, :friends])
|
||||||
end
|
end
|
||||||
|
|
||||||
policy action_type(:create) do
|
policy action_type(:create) do
|
||||||
authorize_if relating_to_actor(:author)
|
authorize_if always()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue