defmodule Permit.Permissions do
@moduledoc """
"""
defstruct conditions_by_action_resource: %{}
alias __MODULE__
alias Permit.Types
alias Permit.Permissions.DNF
@type conditions_by_action_and_resource :: %{
{Types.controller_action(), Types.resource_module()} => DNF.t()
}
@type t :: %Permissions{conditions_by_action_resource: conditions_by_action_and_resource()}
@spec new() :: Permissions.t()
def new, do: %Permissions{}
@spec new(conditions_by_action_and_resource()) :: Permissions.t()
defp new(rca), do: %Permissions{conditions_by_action_resource: rca}
@spec add(Permissions.t(), Types.controller_action(), Types.resource_module(), [
Types.condition()
]) ::
Permissions.t()
def add(permissions, action, resource, conditions) do
permissions.conditions_by_action_resource
|> Map.update({action, resource}, DNF.add_clauses(DNF.new(), conditions), fn
nil -> DNF.add_clauses(DNF.new(), conditions)
dnf -> DNF.add_clauses(dnf, conditions)
end)
|> new()
end
@spec granted?(Permissions.t(), Types.controller_action(), Types.resource(), Types.subject()) ::
boolean()
def granted?(permissions, action, record, subject) do
permissions
|> dnf_for_action_and_record(action, record)
|> DNF.any_satisfied?(record, subject)
end
@spec construct_query(Permissions.t(), Types.controller_action(), Types.resource()) ::
{:ok, Ecto.Query.t()} | {:error, term()}
def construct_query(permissions, action, resource) do
resource = resource_module_from_resource(resource)
permissions.conditions_by_action_resource[{action, resource}]
|> case do
nil ->
{:error, {:undefined_conditions_for, {action, resource}}}
dnf ->
DNF.to_query(dnf, resource)
end
end
@spec dnf_for_action_and_record(Permissions.t(), Types.controller_action(), Types.resource()) ::
DNF.t()
defp dnf_for_action_and_record(permissions, action, resource) do
resource_module = resource_module_from_resource(resource)
permissions.conditions_by_action_resource
|> Map.get({action, resource_module}, DNF.new())
end
@spec resource_module_from_resource(Types.resource()) :: Types.resource_module()
defp resource_module_from_resource(resource) when is_atom(resource),
do: resource
defp resource_module_from_resource(resource) when is_struct(resource),
do: resource.__struct__
end