defmodule Permit do @moduledoc """ Authorization facilities for the application. """ defstruct roles: [], permissions: Permit.Permissions.new(), subject: nil alias Permit.HasRoles alias Permit.Permissions alias Permit.Types @type t :: %Permit{ roles: [Types.role()], permissions: Permissions.t(), subject: Types.subject() | nil } defmacro __using__(opts) do alias Permit.Types permissions_module = Keyword.fetch!(opts, :permissions_module) predicates = permissions_module |> Macro.expand(__CALLER__) |> apply(:actions_module, []) |> apply(:list_groups, []) |> Enum.map(&add_predicate_name/1) |> Enum.map(fn {predicate, name} -> quote do @spec unquote(predicate)(Permit.t(), Types.resource()) :: boolean() def unquote(predicate)(authorization, resource) do Permit.verify_record(authorization, resource, unquote(name)) end end end) quote do @doc """ Initializes a structure holding permissions for a given user role. Returns a Permit struct. """ require unquote(permissions_module) def actions_module, do: unquote(permissions_module).actions_module() @spec can(HasRoles.t()) :: Permit.t() def can(nil), do: raise("Unable to create permit authorization for nil role/user") def can(who) do who |> HasRoles.roles() |> Stream.map(fn role -> unquote(permissions_module).can(role) end) |> Enum.reduce(fn auth1, auth2 -> %Permit{auth1 | permissions: Permissions.join(auth1.permissions, auth2.permissions)} end) |> Map.put(:roles, HasRoles.roles(who)) |> Map.put(:subject, (is_struct(who) && who) || nil) end def resolver_module, do: Permit.Resolver defoverridable resolver_module: 0 unquote(predicates) # defdelegate authorize_and_preload_one! end end @spec has_subject(Permit.t()) :: boolean() def has_subject(%Permit{subject: nil}), do: false def has_subject(%Permit{subject: _}), do: true @spec do?(Permit.t(), Types.action_group(), Types.resource()) :: boolean() def do?(authorization, action, resource) do Permit.verify_record(authorization, action, resource) end # @spec add_permission( # Permit.t(), # Types.action_group(), # Types.resource_module(), # list(), # Types.condition() # ) :: # Permit.t() # def add_permission(authorization, action, resource, bindings, conditions) # when is_list(conditions) do # parsed_conditions = parse_conditions(bindings, conditions) # authorization.permissions # |> Permissions.add(action, resource, parsed_conditions) # |> then(&%Permit{authorization | permissions: &1}) # end @spec verify_record(Permit.t(), Types.resource(), Types.action_group()) :: boolean() def verify_record(authorization, record, action) do authorization.permissions |> Permissions.granted?(action, record, authorization.subject) end defp add_predicate_name(atom), do: {(Atom.to_string(atom) <> "?") |> String.to_atom(), atom} end