Packages
ash
3.21.3
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/policy/policy.ex
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Policy.Policy do
@moduledoc "Represents a policy on an Ash.Resource"
import Crux.Expression, only: [b: 1, is_variable: 1]
alias Ash.Policy.Authorizer
alias Ash.Policy.Check
alias Ash.Policy.FieldPolicy
alias Crux
alias Crux.Expression
alias Crux.Formula
# For now we just write to `checks` and move them to `policies`
# on build, when we support nested policies we can change that.
defstruct [
:condition,
:policies,
:bypass?,
:description,
:access_type,
:__spark_metadata__
]
@type t :: %__MODULE__{
condition: nil | Check.ref() | list(Check.ref()),
policies: list(Check.t()),
bypass?: boolean(),
description: String.t() | nil,
access_type: :strict | :filter | :runtime,
__spark_metadata__: Spark.Dsl.Entity.spark_meta()
}
@spec expression(
policies :: t() | FieldPolicy.t() | [t() | FieldPolicy.t()],
check_context :: Check.context()
) :: Expression.t(Check.ref())
def expression(policies, check_context) do
policies
|> List.wrap()
|> Enum.map(fn policy ->
# Simplification is important here to detect empty field policies
cond_expr = policy |> condition_expression() |> simplify_policy_expression(check_context)
pol_expr = policies_expression(policy)
complete_expr = b(cond_expr and pol_expr)
{policy, cond_expr, complete_expr}
end)
|> List.foldr({false, true}, fn
{%{bypass?: true}, _cond_expr, complete_expr},
{one_condition_matches, all_policies_match} ->
{
# Bypass should only contribute to "at least one policy applies" if it actually authorizes.
# Use complete_expr (condition AND policies) not just condition.
b(complete_expr or one_condition_matches),
b(complete_expr or all_policies_match)
}
{%{}, cond_expr, complete_expr}, {one_condition_matches, all_policies_match} ->
{
b(cond_expr or one_condition_matches),
b(implied_by(complete_expr, cond_expr) and all_policies_match)
}
end)
|> then(&b(elem(&1, 0) and elem(&1, 1)))
|> simplify_policy_expression(check_context)
|> expand_invariants(check_context)
end
@spec solve(authorizer :: Authorizer.t()) ::
{:ok, boolean() | list(map), Authorizer.t()}
| {:error, Authorizer.t(), Ash.Error.t()}
def solve(authorizer) do
check_context = check_context(authorizer)
{expression, authorizer} =
build_requirements_expression(authorizer, check_context)
case expression do
expr when is_boolean(expr) ->
{:ok, expr, authorizer}
expression ->
scenario_options = scenario_options(check_context)
expression
|> Formula.from_expression()
|> Crux.satisfying_scenarios(scenario_options)
|> case do
[] ->
{:error, authorizer, :unsatisfiable}
scenarios ->
mapped_scenarios = Enum.map(scenarios, &Map.drop(&1, [true, false]))
{:ok, Enum.uniq(mapped_scenarios), authorizer}
end
end
catch
{:error, authorizer, error} ->
{:error, authorizer, error}
end
@doc false
@spec transform(policy :: t()) :: {:ok, t()} | {:error, String.t()}
def transform(policy) do
cond do
policy.policies |> List.wrap() |> Enum.empty?() ->
{:error, "Policies must have at least one check."}
policy.bypass? &&
Enum.all?(List.wrap(policy.policies), &(&1.type in [:forbid_if, :forbid_unless])) ->
{:error,
"""
Bypass policies that can only ever forbid have no effect.
When a bypass is authorized, it skips all remaining policies (including other bypasses)
and authorizes the request. If it fails, it is ignored and the remaining policies are checked.
This policy only contains `forbid_if` or `forbid_unless` check types therefore, it can
never have an effect.
"""}
policy.condition |> List.wrap() |> Enum.empty?() ->
{:ok, %{policy | condition: [{Check.Static, result: true}]}}
true ->
{:ok, policy}
end
end
@spec build_requirements_expression(
authorizer :: Authorizer.t(),
check_context :: Check.context()
) :: {Expression.t(Check.ref()), Authorizer.t()}
defp build_requirements_expression(authorizer, check_context) do
{expression, authorizer} =
authorizer.policies
|> expression(check_context)
|> simplify_policy_expression(check_context)
|> expand_constants(authorizer, check_context)
authorizer = %{authorizer | solver_statement: expression}
{expression, authorizer}
end
@spec fetch_or_strict_check_fact(
Authorizer.t(),
Check.t() | Check.ref()
) ::
{:ok, Expression.t(Check.ref()), Authorizer.t()}
| {:error, Authorizer.t()}
def fetch_or_strict_check_fact(authorizer, check)
def fetch_or_strict_check_fact(authorizer, %Check{check_module: mod, check_opts: opts}),
do: fetch_or_strict_check_fact(authorizer, {mod, opts})
def fetch_or_strict_check_fact(authorizer, check) when is_atom(check),
do: fetch_or_strict_check_fact(authorizer, {check, []})
def fetch_or_strict_check_fact(authorizer, {Check.Static, opts}),
do: {:ok, opts[:result], authorizer}
def fetch_or_strict_check_fact(authorizer, {check_module, opts}) do
authorizer.facts
|> Enum.find_value(fn
{{fact_mod, fact_opts}, result} when result != :unknown ->
if check_module == fact_mod &&
Keyword.drop(fact_opts, [:access_type, :ash_field_policy?]) ==
Keyword.drop(opts, [:access_type, :ash_field_policy?]) do
{:ok, result}
end
_ ->
nil
end)
|> case do
nil ->
if check_module.requires_original_data?(authorizer, opts) &&
missing_original_data?(authorizer) do
throw(
{:error, authorizer,
Ash.Error.Forbidden.InitialDataRequired.exception(
source: "check: #{check_module.describe(opts)} requires initial data"
)}
)
else
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} ->
throw({:error, authorizer, Ash.Error.to_ash_error(error)})
end
end
{:ok, :unknown} ->
{:error, authorizer}
{:ok, value} ->
{:ok, value, authorizer}
end
end
@spec missing_original_data?(authorizer :: Authorizer.t()) :: boolean()
defp missing_original_data?(%Authorizer{
changeset: %Ash.Changeset{data: %Ash.Changeset.OriginalDataNotAvailable{}}
}) do
true
end
defp missing_original_data?(_), do: false
@spec fetch_fact(facts :: map, check :: Check.t() | Check.ref()) ::
{:ok, Expression.t(Check.ref())} | :error
def fetch_fact(facts, check)
def fetch_fact(facts, %{check_module: mod, check_opts: opts}),
do: fetch_fact(facts, {mod, opts})
def fetch_fact(_facts, {Check.Static, opts}), do: {:ok, opts[:result]}
def fetch_fact(facts, {mod, opts}) do
Enum.find_value(facts, fn
{{fact_mod, fact_opts}, result} ->
if mod == fact_mod &&
Keyword.drop(fact_opts, [:access_type, :ash_field_policy?]) ==
Keyword.drop(opts, [:access_type, :ash_field_policy?]) do
{:ok, result}
end
_ ->
nil
end)
|> case do
nil ->
:error
:unknown ->
:error
value ->
value
end
end
@spec condition_expression(policy :: t() | FieldPolicy.t()) :: Expression.t(Check.ref())
defp condition_expression(%{condition: condition}) do
condition
|> List.wrap()
|> Enum.reduce(true, &b(&2 and &1))
end
@spec policies_expression(policy :: t() | FieldPolicy.t()) :: Expression.t(Check.ref())
defp policies_expression(%{policies: policies}) do
policies
|> List.wrap()
|> List.foldr(false, fn
%Check{type: :authorize_if} = clause, acc ->
b({clause.check_module, clause.check_opts} or acc)
%Check{type: :authorize_unless} = clause, acc ->
b(implies({clause.check_module, clause.check_opts}, acc))
%Check{type: :forbid_if} = clause, acc ->
b(not {clause.check_module, clause.check_opts} and acc)
%Check{type: :forbid_unless} = clause, acc ->
b({clause.check_module, clause.check_opts} and acc)
end)
end
@spec expand_constants(
expression :: Expression.t(Check.ref()),
authorizer :: Authorizer.t(),
check_context :: Check.context()
) :: {Expression.t(Check.ref()), Authorizer.t()}
defp expand_constants(expression, authorizer, check_context) do
{expression, authorizer} =
Expression.expand(expression, authorizer, fn
expr, authorizer when is_variable(expr) ->
case fetch_or_strict_check_fact(authorizer, expr) do
{:ok, result, authorizer} ->
{result, authorizer}
{:error, authorizer} ->
{expr, authorizer}
end
other, authorizer ->
{other, authorizer}
end)
{simplify_policy_expression(expression, check_context), authorizer}
end
@spec simplify_policy_expression(
expression :: Expression.t(Check.ref()),
context :: Check.context()
) :: Expression.t(Check.ref())
defp simplify_policy_expression(expression, context) do
expression
|> Expression.postwalk(fn
{check, _opts} = expr when is_variable(expr) ->
Code.ensure_loaded!(check)
if function_exported?(check, :simplify, 2) do
check.simplify(expr, context)
else
expr
end
other ->
other
end)
|> Expression.simplify()
end
@doc false
@spec debug_expr(expr :: Expression.t(Check.ref()), label :: String.t()) :: String.t()
def debug_expr(expr, label \\ "Expr") do
expr
|> Crux.Expression.to_string(fn
{check_module, check_opts} -> check_module.describe(check_opts)
v -> Macro.escape(v)
end)
|> then(&"#{label}:\n\n#{&1}")
end
@spec check_context(authorizer :: Authorizer.t()) :: Check.context()
defp check_context(%Authorizer{resource: resource}) do
%{resource: resource}
end
@spec expand_invariants(
expression :: Expression.t(Check.ref()),
check_context :: Check.context()
) :: Expression.t(Check.ref())
defp expand_invariants(expression, check_context) do
{_, variables} =
Expression.postwalk(expression, [], fn
check, acc when is_variable(check) -> {check, [check | acc]}
other, acc -> {other, acc}
end)
unique_variables = Enum.uniq(variables)
for {check, _} <- unique_variables do
Code.ensure_loaded!(check)
end
expression =
for {check, _} = left <- unique_variables,
right <- unique_variables,
left != right,
reduce: expression do
acc ->
cond do
not function_exported?(check, :implies?, 3) -> acc
check.implies?(left, right, check_context) -> b(acc and implies(left, right))
true -> acc
end
end
for {check, _} = left <- unique_variables,
right <- unique_variables,
left != right,
reduce: expression do
acc ->
cond do
not function_exported?(check, :conflict?, 3) -> acc
check.conflict?(left, right, check_context) -> b(acc and nand(left, right))
true -> acc
end
end
end
@check_priorities [
Ash.Policy.Check.Static,
Ash.Policy.Check.Action,
Ash.Policy.Check.ActionType,
Ash.Policy.Check.PrivateAction,
Ash.Policy.Check.ActorAbsent,
Ash.Policy.Check.ActorPresent
]
|> Enum.with_index()
|> Map.new()
@doc """
Default Options for Crux scenarios
"""
@spec scenario_options(check_context :: Check.context()) :: Crux.opts(Check.ref())
def scenario_options(check_context) do
[
sorter: fn left, right ->
left_priority = Map.get(@check_priorities, elem(left, 0), 1_000)
right_priority = Map.get(@check_priorities, elem(right, 0), 1_000)
if left_priority != right_priority do
left_priority <= right_priority
else
left <= right
end
end,
conflicts?: fn {check, _otps} = left, right ->
function_exported?(check, :conflicts?, 3) and
check.conflicts?(left, right, check_context)
end,
implies?: fn {check, _opts} = left, right ->
function_exported?(check, :implies?, 3) and
check.implies?(left, right, check_context)
end
]
end
end