defmodule Permit do
@moduledoc """
Authorization facilities for the application.
"""
defstruct role: nil, permissions: Permit.Permissions.new(), subject: nil
alias Permit.Types
alias Permit.Permissions
@type t :: %Permit{
role: Types.role(),
permissions: Permissions.t(),
subject: Types.subject() | nil
}
defmacro __using__(opts) do
alias Permit.Types
permissions_module = Keyword.fetch!(opts, :permissions_module)
quote do
@doc """
Initializes a structure holding permissions for a given user role.
Returns a Permit struct.
"""
@spec can(Types.subject_with_role()) :: Permit.t()
def can(%{role: role} = subject) when is_struct(subject),
do: can(role, subject)
@spec can(Types.role_record(), Types.subject() | nil) :: Permit.t()
def can(role, subject \\ nil)
def can(role, nil) when is_map(role) do
unquote(permissions_module).can(role)
end
def can(role, subject) when is_map(role) do
can(role)
|> Permit.put_subject(subject)
end
@spec read?(Permit.t(), Types.resource()) :: boolean()
def read?(authorization, resource) do
Permit.verify_record(authorization, resource, :read)
end
@spec create?(Permit.t(), Types.resource()) :: boolean()
def create?(authorization, resource) do
Permit.verify_record(authorization, resource, :create)
end
@spec update?(Permit.t(), Types.resource()) :: boolean()
def update?(authorization, resource) do
Permit.verify_record(authorization, resource, :update)
end
@spec delete?(Permit.t(), Types.resource()) :: boolean()
def delete?(authorization, resource) do
Permit.verify_record(authorization, resource, :delete)
end
@spec repo() :: Ecto.Repo.t()
def repo, do: unquote(opts[:repo])
@spec accessible_by(Types.subject(), Types.controller_action(), Types.resource()) ::
{:ok, Ecto.Query.t()} | {:error, term()}
def accessible_by(current_user, action, resource) do
unquote(permissions_module)
|> apply(:can, [current_user])
|> Map.get(:permissions)
|> Permissions.construct_query(action, resource)
end
end
end
@spec put_subject(Permit.t(), Types.role()) :: Permit.t()
def put_subject(authorization, subject) do
%Permit{authorization | subject: subject}
end
@spec add_permission(Permit.t(), Types.controller_action(), Types.resource_module(), [
Types.condition()
]) ::
Permit.t()
def add_permission(authorization, action, resource, conditions) when is_list(conditions) do
updated_permissions =
authorization.permissions
|> Permissions.add(action, resource, conditions)
%Permit{authorization | permissions: updated_permissions}
end
@spec verify_record(Permit.t(), Types.resource(), Types.crud()) :: boolean()
def verify_record(authorization, record, action) do
authorization.permissions
|> Permissions.granted?(action, record, authorization.subject)
end
end