mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: optimize policy check running with laziness
Implemented lazy evaluation of individual checks, so that checks that are demonstrably irrelevant when building policies are not checked at all. This will often mean no need to visit the sat solver at all, or only with a very minimal set of filter checks.
This commit is contained in:
parent
d6178a026c
commit
b899a6ecf3
10 changed files with 451 additions and 201 deletions
|
@ -537,7 +537,7 @@ defmodule Ash.Api do
|
|||
{:error, error} ->
|
||||
{:halt, {:error, error}}
|
||||
|
||||
:authorized ->
|
||||
{:authorized, _} ->
|
||||
{:cont, {true, query}}
|
||||
|
||||
{:filter, _authorizer, filter} ->
|
||||
|
|
|
@ -17,7 +17,7 @@ defmodule Ash.Authorizer do
|
|||
) :: state
|
||||
@callback strict_check_context(state) :: [atom]
|
||||
@callback strict_check(state, context) ::
|
||||
:authorized
|
||||
{:authorized, state}
|
||||
| {:continue, state}
|
||||
| {:filter, Keyword.t()}
|
||||
| {:filter, Keyword.t(), state}
|
||||
|
|
|
@ -552,7 +552,7 @@ defmodule Ash.Engine.Request do
|
|||
case missing_strict_check_dependencies?(authorizer, request) do
|
||||
[] ->
|
||||
case strict_check_authorizer(authorizer, request) do
|
||||
:authorized ->
|
||||
{:authorized, _authorizer} ->
|
||||
{:ok, set_authorizer_state(request, authorizer, :authorized), notifications, []}
|
||||
|
||||
{:filter, authorizer_state, filter} ->
|
||||
|
|
|
@ -317,7 +317,8 @@ defmodule Ash.Error.Forbidden.Policy do
|
|||
"⛔"
|
||||
|
||||
{:unknown, :unknown} ->
|
||||
if success? && filter_check? && Policy.fetch_fact(facts, check.check) == :error do
|
||||
if (success? && filter_check? && Policy.fetch_fact(facts, check.check) == :error) ||
|
||||
{:ok, :unknown} do
|
||||
"🔎"
|
||||
else
|
||||
"⬇"
|
||||
|
|
|
@ -369,24 +369,21 @@ defmodule Ash.Policy.Authorizer do
|
|||
api: context.api
|
||||
}
|
||||
|> get_policies()
|
||||
|> do_strict_check_facts()
|
||||
|> strict_check_result()
|
||||
|> case do
|
||||
{:ok, authorizer} ->
|
||||
case strict_check_result(authorizer) do
|
||||
:authorized ->
|
||||
{:authorized, authorizer} ->
|
||||
log_successful_policy_breakdown(authorizer)
|
||||
:authorized
|
||||
{:authorized, authorizer}
|
||||
|
||||
{:filter, authorizer, filter} ->
|
||||
log_successful_policy_breakdown(authorizer, filter)
|
||||
{:filter, authorizer, filter}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
|
||||
{other, _authorizer} ->
|
||||
other
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -409,11 +406,11 @@ defmodule Ash.Policy.Authorizer do
|
|||
|
||||
case {filter, require_check} do
|
||||
{[], []} ->
|
||||
:authorized
|
||||
{:authorized, authorizer}
|
||||
|
||||
{_filters, []} ->
|
||||
if Enum.any?(filter, &(&1 == true)) do
|
||||
:authorized
|
||||
{:authorized, authorizer}
|
||||
else
|
||||
case filter do
|
||||
[filter] ->
|
||||
|
@ -759,7 +756,20 @@ defmodule Ash.Policy.Authorizer do
|
|||
|
||||
defp strict_check_result(authorizer) do
|
||||
case Checker.strict_check_scenarios(authorizer) do
|
||||
{:ok, scenarios} ->
|
||||
{:ok, true, authorizer} ->
|
||||
{:authorized, authorizer}
|
||||
|
||||
{:ok, false, authorizer} ->
|
||||
{:error,
|
||||
Ash.Error.Forbidden.Policy.exception(
|
||||
facts: authorizer.facts,
|
||||
policies: authorizer.policies,
|
||||
resource: Map.get(authorizer, :resource),
|
||||
action: Map.get(authorizer, :action),
|
||||
scenarios: []
|
||||
)}
|
||||
|
||||
{:ok, scenarios, authorizer} ->
|
||||
report_scenarios(authorizer, scenarios, "Potential Scenarios")
|
||||
|
||||
case Checker.find_real_scenarios(scenarios, authorizer.facts) do
|
||||
|
@ -768,7 +778,7 @@ defmodule Ash.Policy.Authorizer do
|
|||
|
||||
real_scenarios ->
|
||||
report_scenarios(authorizer, real_scenarios, "Real Scenarios")
|
||||
:authorized
|
||||
{:authorized, authorizer}
|
||||
end
|
||||
|
||||
{:error, :unsatisfiable} ->
|
||||
|
@ -788,16 +798,6 @@ defmodule Ash.Policy.Authorizer do
|
|||
strict_filter(%{authorizer | scenarios: scenarios})
|
||||
end
|
||||
|
||||
defp do_strict_check_facts(authorizer) do
|
||||
case Checker.strict_check_facts(authorizer) do
|
||||
{:ok, authorizer, new_facts} ->
|
||||
{:ok, %{authorizer | facts: new_facts}}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_policies(authorizer) do
|
||||
%{
|
||||
authorizer
|
||||
|
|
|
@ -117,11 +117,14 @@ defmodule Ash.Policy.Checker do
|
|||
|
||||
def strict_check_scenarios(authorizer) do
|
||||
case Ash.Policy.Policy.solve(authorizer) do
|
||||
{:ok, scenarios} ->
|
||||
{:ok, value, authorizer} when is_boolean(value) ->
|
||||
{:ok, value, authorizer}
|
||||
|
||||
{:ok, scenarios, authorizer} ->
|
||||
{:ok,
|
||||
scenarios
|
||||
|> Ash.Policy.SatSolver.simplify_clauses()
|
||||
|> remove_scenarios_with_impossible_facts(authorizer)}
|
||||
|> remove_scenarios_with_impossible_facts(authorizer), authorizer}
|
||||
|
||||
{:error, :unsatisfiable} ->
|
||||
{:error, :unsatisfiable}
|
||||
|
|
|
@ -49,7 +49,7 @@ defmodule Ash.Policy.Info do
|
|||
{:error, _error} ->
|
||||
false
|
||||
|
||||
:authorized ->
|
||||
{:authorized, _} ->
|
||||
true
|
||||
|
||||
{:filter, _, _} ->
|
||||
|
@ -78,7 +78,7 @@ defmodule Ash.Policy.Info do
|
|||
{:error, _error} ->
|
||||
false
|
||||
|
||||
:authorized ->
|
||||
{:authorized, _} ->
|
||||
true
|
||||
|
||||
{:filter, _, _} ->
|
||||
|
@ -242,7 +242,7 @@ defmodule Ash.Policy.Info do
|
|||
end
|
||||
|
||||
defp run_check(actor, query, api: api, maybe_is: maybe_is) do
|
||||
case Ash.Policy.Info.strict_check(actor, query, api) do
|
||||
case strict_check(actor, query, api) do
|
||||
true ->
|
||||
true
|
||||
|
||||
|
|
|
@ -23,77 +23,101 @@ defmodule Ash.Policy.Policy do
|
|||
|
||||
def solve(authorizer) do
|
||||
authorizer.policies
|
||||
|> build_requirements_expression(authorizer.facts)
|
||||
|> Ash.Policy.SatSolver.solve(fn scenario, bindings ->
|
||||
|> build_requirements_expression(authorizer)
|
||||
|> case do
|
||||
{true, authorizer} ->
|
||||
{:ok, true, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{:ok, false, authorizer}
|
||||
|
||||
{expression, authorizer} ->
|
||||
Ash.Policy.SatSolver.solve(expression, fn scenario, bindings ->
|
||||
scenario
|
||||
|> Ash.SatSolver.solutions_to_predicate_values(bindings)
|
||||
|> Map.drop(@static_checks)
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_requirements_expression(policies, facts) do
|
||||
at_least_one_policy_expression = at_least_one_policy_expression(policies, facts)
|
||||
|
||||
if at_least_one_policy_expression == false do
|
||||
false
|
||||
else
|
||||
policy_expression =
|
||||
if at_least_one_policy_expression == true do
|
||||
compile_policy_expression(policies, facts)
|
||||
else
|
||||
case {:and, at_least_one_policy_expression, compile_policy_expression(policies, facts)} do
|
||||
{:and, false, _} ->
|
||||
false
|
||||
|
||||
{:and, _, false} ->
|
||||
false
|
||||
|
||||
{:and, true, true} ->
|
||||
true
|
||||
|
||||
{:and, left, true} ->
|
||||
left
|
||||
|
||||
{:and, true, right} ->
|
||||
right
|
||||
|> case do
|
||||
{:ok, scenarios} ->
|
||||
{:ok, scenarios, authorizer}
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
used_facts = used_facts(policy_expression)
|
||||
defp build_requirements_expression(policies, authorizer) do
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(policies, authorizer)
|
||||
|
||||
if at_least_one_policy_expression == false do
|
||||
{false, authorizer}
|
||||
else
|
||||
{policy_expression, authorizer} =
|
||||
if at_least_one_policy_expression == true do
|
||||
compile_policy_expression(policies, authorizer)
|
||||
else
|
||||
{policy_expression, authorizer} = compile_policy_expression(policies, authorizer)
|
||||
|
||||
case {:and, at_least_one_policy_expression, policy_expression} do
|
||||
{:and, false, _} ->
|
||||
{false, authorizer}
|
||||
|
||||
{:and, _, false} ->
|
||||
{false, authorizer}
|
||||
|
||||
{:and, true, true} ->
|
||||
{true, authorizer}
|
||||
|
||||
{:and, left, true} ->
|
||||
{left, authorizer}
|
||||
|
||||
{:and, true, right} ->
|
||||
{right, authorizer}
|
||||
|
||||
other ->
|
||||
{other, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
facts_expression =
|
||||
facts
|
||||
authorizer.facts
|
||||
|> Map.drop([true, false])
|
||||
|> Map.take(MapSet.to_list(used_facts))
|
||||
|> Ash.Policy.SatSolver.facts_to_statement()
|
||||
|
||||
if facts_expression do
|
||||
{:and, facts_expression, policy_expression}
|
||||
{{:and, facts_expression, policy_expression}, authorizer}
|
||||
else
|
||||
policy_expression
|
||||
{policy_expression, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp used_facts({_op, l, r}) do
|
||||
MapSet.union(used_facts(l), used_facts(r))
|
||||
end
|
||||
|
||||
defp used_facts({:not, fact}) do
|
||||
used_facts(fact)
|
||||
end
|
||||
|
||||
defp used_facts(other) do
|
||||
MapSet.new([other])
|
||||
end
|
||||
|
||||
def at_least_one_policy_expression(policies, facts) do
|
||||
def at_least_one_policy_expression(policies, authorizer) do
|
||||
policies
|
||||
|> Enum.map(&condition_expression(&1.condition, facts))
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.reduce({[], authorizer}, fn
|
||||
policy, {condition_exprs, authorizer} when is_list(condition_exprs) ->
|
||||
case condition_expression(policy.condition, authorizer) do
|
||||
{nil, authorizer} ->
|
||||
{condition_exprs, authorizer}
|
||||
|
||||
{true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{condition_expr, authorizer} ->
|
||||
{[condition_expr | condition_exprs], authorizer}
|
||||
end
|
||||
|
||||
_, {true, authorizer} ->
|
||||
{true, authorizer}
|
||||
end)
|
||||
|> then(fn
|
||||
{true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{condition_exprs, authorizer} ->
|
||||
{condition_exprs
|
||||
|> Enum.reduce(false, fn
|
||||
_, true ->
|
||||
true
|
||||
|
@ -106,9 +130,53 @@ defmodule Ash.Policy.Policy do
|
|||
|
||||
condition, acc ->
|
||||
{:or, condition, acc}
|
||||
end), authorizer}
|
||||
end)
|
||||
end
|
||||
|
||||
def fetch_or_strict_check_fact(authorizer, %{check_module: mod, check_opts: opts}) do
|
||||
fetch_or_strict_check_fact(authorizer, {mod, opts})
|
||||
end
|
||||
|
||||
def fetch_or_strict_check_fact(authorizer, {check_module, opts}) do
|
||||
Enum.find_value(authorizer.facts, fn
|
||||
{{fact_mod, fact_opts}, result} ->
|
||||
if check_module == fact_mod &&
|
||||
Keyword.delete(fact_opts, :access_type) ==
|
||||
Keyword.delete(opts, :access_type) do
|
||||
{:ok, result}
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
case check_module.strict_check(authorizer.actor, authorizer, opts) do
|
||||
{:ok, value} when is_boolean(value) or value == :unknown ->
|
||||
authorizer = %{
|
||||
authorizer
|
||||
| facts: Map.put(authorizer.facts, {check_module, opts}, value)
|
||||
}
|
||||
|
||||
if value == :unknown do
|
||||
{:error, authorizer}
|
||||
else
|
||||
{:ok, value, authorizer}
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
raise "Error produced by #{check_module}'s strict_checking logic: #{inspect(error)}"
|
||||
end
|
||||
|
||||
{:ok, :unknown} ->
|
||||
{:error, authorizer}
|
||||
|
||||
{:ok, value} ->
|
||||
{:ok, value, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_fact(facts, %{check_module: mod, check_opts: opts}) do
|
||||
fetch_fact(facts, {mod, opts})
|
||||
end
|
||||
|
@ -132,72 +200,77 @@ defmodule Ash.Policy.Policy do
|
|||
nil ->
|
||||
:error
|
||||
|
||||
:unknown ->
|
||||
:error
|
||||
|
||||
value ->
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp condition_expression(condition, facts) do
|
||||
defp condition_expression(condition, authorizer) do
|
||||
condition
|
||||
|> List.wrap()
|
||||
|> Enum.reduce(nil, fn
|
||||
condition, nil ->
|
||||
case fetch_fact(facts, condition) do
|
||||
{:ok, true} ->
|
||||
true
|
||||
|> Enum.reduce({nil, authorizer}, fn
|
||||
condition, {nil, authorizer} ->
|
||||
case fetch_or_strict_check_fact(authorizer, condition) do
|
||||
{:ok, true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
false
|
||||
{:ok, false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
_ ->
|
||||
condition
|
||||
{condition, authorizer}
|
||||
end
|
||||
|
||||
_condition, false ->
|
||||
false
|
||||
_condition, {false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
condition, expression ->
|
||||
case fetch_fact(facts, condition) do
|
||||
{:ok, true} ->
|
||||
expression
|
||||
condition, {expression, authorizer} ->
|
||||
case fetch_or_strict_check_fact(authorizer, condition) do
|
||||
{:ok, true, authorizer} ->
|
||||
{expression, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
false
|
||||
{:ok, false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
_ ->
|
||||
{:and, condition, expression}
|
||||
{{:and, condition, expression}, authorizer}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp compile_policy_expression(policies, facts)
|
||||
|
||||
defp compile_policy_expression([], _facts) do
|
||||
false
|
||||
defp compile_policy_expression([], authorizer) do
|
||||
{false, authorizer}
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%__MODULE__{condition: condition, policies: policies}],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
compiled_policies = compile_policy_expression(policies, facts)
|
||||
condition_expression = condition_expression(condition, facts)
|
||||
{condition_expression, authorizer} = condition_expression(condition, authorizer)
|
||||
|
||||
case condition_expression do
|
||||
true ->
|
||||
compiled_policies
|
||||
compile_policy_expression(policies, authorizer)
|
||||
|
||||
false ->
|
||||
true
|
||||
{true, authorizer}
|
||||
|
||||
nil ->
|
||||
compiled_policies
|
||||
compile_policy_expression(policies, authorizer)
|
||||
|
||||
condition_expression ->
|
||||
if compiled_policies == true do
|
||||
condition_expression
|
||||
else
|
||||
{:and, condition_expression, compiled_policies}
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{condition_expression, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{{:not, condition_expression}, authorizer}
|
||||
|
||||
{compiled_policies, authorizer} ->
|
||||
{{:and, condition_expression, compiled_policies}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -206,149 +279,315 @@ defmodule Ash.Policy.Policy do
|
|||
[
|
||||
%__MODULE__{condition: condition, policies: policies, bypass?: bypass?} | rest
|
||||
],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
condition_expression = condition_expression(condition, facts)
|
||||
{condition_expression, authorizer} = condition_expression(condition, authorizer)
|
||||
|
||||
case condition_expression do
|
||||
true ->
|
||||
if bypass? do
|
||||
{:or, compile_policy_expression(policies, facts),
|
||||
{:and, compile_policy_expression(rest, facts),
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), facts)}}
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), authorizer)
|
||||
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
|
||||
{{:and, rest, at_least_one_policy_expression}, authorizer}
|
||||
|
||||
{policy_expression, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), authorizer)
|
||||
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
{{:or, policy_expression, {:and, rest, at_least_one_policy_expression}}, authorizer}
|
||||
end
|
||||
else
|
||||
{:and, compile_policy_expression(policies, facts),
|
||||
compile_policy_expression(rest, facts)}
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{true, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
{policy_expression, authorizer} ->
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{true, authorizer} ->
|
||||
{policy_expression, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:and, policy_expression, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
false ->
|
||||
compile_policy_expression(rest, facts)
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
nil ->
|
||||
if bypass? do
|
||||
{:or, compile_policy_expression(policies, facts),
|
||||
{:and, compile_policy_expression(rest, facts),
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), facts)}}
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), authorizer)
|
||||
|
||||
rest = compile_policy_expression(rest, authorizer)
|
||||
{:and, rest, at_least_one_policy_expression}
|
||||
|
||||
{policy_expression, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), authorizer)
|
||||
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{false, authorizer} ->
|
||||
{policy_expression, authorizer}
|
||||
|
||||
{true, authorizer} ->
|
||||
{{:or, policy_expression, at_least_one_policy_expression}, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:or, policy_expression, {:and, rest, at_least_one_policy_expression}},
|
||||
authorizer}
|
||||
end
|
||||
end
|
||||
else
|
||||
{:and, compile_policy_expression(policies, facts),
|
||||
compile_policy_expression(rest, facts)}
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{policy_expression, authorizer} ->
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{policy_expression, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:and, policy_expression, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
condition_expression ->
|
||||
if bypass? do
|
||||
{:or, {:and, condition_expression, compile_policy_expression(policies, facts)},
|
||||
{:and, compile_policy_expression(rest, facts),
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), facts)}}
|
||||
{at_least_one_policy_expression, authorizer} =
|
||||
at_least_one_policy_expression(Enum.take_while(rest, &(!&1.bypass?)), authorizer)
|
||||
|
||||
{condition_and_policy_expression, authorizer} =
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{condition_expression, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{other, authorizer} ->
|
||||
{{:and, condition_expression, other}, authorizer}
|
||||
end
|
||||
|
||||
case condition_and_policy_expression do
|
||||
true ->
|
||||
{true, authorizer}
|
||||
|
||||
false ->
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:and, rest, at_least_one_policy_expression}, authorizer}
|
||||
end
|
||||
|
||||
condition_and_policy_expression ->
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{at_least_one_policy_expression, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{condition_and_policy_expression, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:or, condition_and_policy_expression,
|
||||
{:and, rest, at_least_one_policy_expression}}, authorizer}
|
||||
end
|
||||
end
|
||||
else
|
||||
{:or, {:and, condition_expression, compile_policy_expression(policies, facts)},
|
||||
{:and, {:not, condition_expression}, compile_policy_expression(rest, facts)}}
|
||||
{condition_and_policy_expression, authorizer} =
|
||||
case compile_policy_expression(policies, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{condition, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{policy_expression, authorizer} ->
|
||||
{{:and, condition, policy_expression}, authorizer}
|
||||
end
|
||||
|
||||
case condition_and_policy_expression do
|
||||
false ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
true ->
|
||||
{true, authorizer}
|
||||
|
||||
condition_and_policy_expression ->
|
||||
case compile_policy_expression(rest, authorizer) do
|
||||
{true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{false, authorizer} ->
|
||||
{condition_and_policy_expression, authorizer}
|
||||
|
||||
{rest, authorizer} ->
|
||||
{{:or, condition_and_policy_expression, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :authorize_if} = clause],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
true
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
false
|
||||
{:ok, false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
:error ->
|
||||
{clause.check_module, clause.check_opts}
|
||||
{:error, authorizer} ->
|
||||
{{clause.check_module, clause.check_opts}, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :authorize_if} = clause | rest],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
true
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
compile_policy_expression(rest, facts)
|
||||
{:ok, false, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
:error ->
|
||||
{:or, {clause.check_module, clause.check_opts}, compile_policy_expression(rest, facts)}
|
||||
{:error, authorizer} ->
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
{{:or, {clause.check_module, clause.check_opts}, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :authorize_unless} = clause],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
false
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
true
|
||||
{:ok, false, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
:error ->
|
||||
{clause.check_module, clause.check_opts}
|
||||
{:error, authorizer} ->
|
||||
{{clause.check_module, clause.check_opts}, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :authorize_unless} = clause | rest],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
compile_policy_expression(rest, facts)
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
{:ok, false} ->
|
||||
true
|
||||
{:ok, false, authorizer} ->
|
||||
{true, authorizer}
|
||||
|
||||
:error ->
|
||||
{:or, {:not, {clause.check_module, clause.check_opts}},
|
||||
compile_policy_expression(rest, facts)}
|
||||
{:error, authorizer} ->
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
{{:or, {:not, {clause.check_module, clause.check_opts}}, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression([%{type: :forbid_if}], _facts) do
|
||||
false
|
||||
defp compile_policy_expression([%{type: :forbid_if}], authorizer) do
|
||||
{false, authorizer}
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :forbid_if} = clause | rest],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
false
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
{:ok, false} ->
|
||||
compile_policy_expression(rest, facts)
|
||||
{:ok, false, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
:error ->
|
||||
{:and, {:not, {clause.check_module, clause.check_opts}},
|
||||
compile_policy_expression(rest, facts)}
|
||||
{:error, authorizer} ->
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
|
||||
case rest do
|
||||
true ->
|
||||
{{:not, {clause.check_module, clause.check_opts}}, authorizer}
|
||||
|
||||
false ->
|
||||
{false, authorizer}
|
||||
|
||||
rest ->
|
||||
{{:and, {:not, {clause.check_module, clause.check_opts}}, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_policy_expression([%{type: :forbid_unless}], _facts) do
|
||||
false
|
||||
defp compile_policy_expression([%{type: :forbid_unless}], authorizer) do
|
||||
{false, authorizer}
|
||||
end
|
||||
|
||||
defp compile_policy_expression(
|
||||
[%{type: :forbid_unless} = clause | rest],
|
||||
facts
|
||||
authorizer
|
||||
) do
|
||||
case fetch_fact(facts, clause) do
|
||||
{:ok, true} ->
|
||||
compile_policy_expression(rest, facts)
|
||||
case fetch_or_strict_check_fact(authorizer, clause) do
|
||||
{:ok, true, authorizer} ->
|
||||
compile_policy_expression(rest, authorizer)
|
||||
|
||||
{:ok, false} ->
|
||||
false
|
||||
{:ok, false, authorizer} ->
|
||||
{false, authorizer}
|
||||
|
||||
:error ->
|
||||
{:and, {clause.check_module, clause.check_opts}, compile_policy_expression(rest, facts)}
|
||||
{:error, authorizer} ->
|
||||
{rest, authorizer} = compile_policy_expression(rest, authorizer)
|
||||
|
||||
case rest do
|
||||
false ->
|
||||
{false, authorizer}
|
||||
|
||||
true ->
|
||||
{{clause.check_module, clause.check_opts}, authorizer}
|
||||
|
||||
rest ->
|
||||
{{:and, {clause.check_module, clause.check_opts}, rest}, authorizer}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -109,7 +109,11 @@ defmodule Ash.Policy.SatSolver do
|
|||
end
|
||||
|
||||
def facts_to_statement(facts) do
|
||||
Enum.reduce(facts, nil, fn {fact, true?}, expr ->
|
||||
Enum.reduce(facts, nil, fn
|
||||
{_fact, :unknown}, expr ->
|
||||
expr
|
||||
|
||||
{fact, true?}, expr ->
|
||||
expr_component =
|
||||
if true? do
|
||||
fact
|
||||
|
|
|
@ -27,7 +27,7 @@ defmodule Ash.Test.Authorizer do
|
|||
def strict_check_context(_), do: get(:strict_check_context, [])
|
||||
|
||||
def strict_check(state, _),
|
||||
do: get(:strict_check_result, :authorized) |> continue(state)
|
||||
do: get(:strict_check_result, :authorized) |> continue(state) |> wrap_authorized(state)
|
||||
|
||||
def check_context(_), do: []
|
||||
|
||||
|
@ -36,6 +36,9 @@ defmodule Ash.Test.Authorizer do
|
|||
defp continue(:continue, state), do: {:continue, state}
|
||||
defp continue(other, _), do: other
|
||||
|
||||
defp wrap_authorized(:authorized, state), do: {:authorized, state}
|
||||
defp wrap_authorized(other, _), do: other
|
||||
|
||||
defp get(key, default) do
|
||||
Agent.get(__MODULE__, &Map.get(&1, key)) || default
|
||||
catch
|
||||
|
|
Loading…
Reference in a new issue