lib/cap.ex

defmodule Cap do
  @moduledoc """
  Documentation for `Cap`.
  """
  use Plug.Builder
  alias Cap.{Rbac, Abac, Crypto}

  def init(opts) do
    Keyword.merge(Application.get_all_env(:cap), opts)
  end

  def call(conn, opts) do
    check_config = Keyword.has_key?(opts, :policy) && Keyword.has_key?(opts, :effect)

    case check_config do
      true -> apply_cap(conn, opts)
      _ -> conn
    end
  end

  @doc """
  Call sign_in in controller login to put session.

   ## Example

       Cap.sign_in(conn, id, role)

  """
  def sign_in(conn, id, role) do
    string = %{id: id, role: role}

    conn
    |> put_session(:cap, string)
    |> configure_session(renew: true)
  end

  @doc """
  Verify password.

  ## Example
      iex>  Cap.verify_pwd("password", "9+757UiM5YnNcyQKtgCPx9IJb2pjv8sJgxCBQqC9kX0")
      true
  """
  def verify_pwd(pass, hash) do
    Crypto.verify_sha(pass, hash)
  end

  @doc """
  Hash password.

  ## Example
      iex>  Cap.hash_pwd("password")
      "9+757UiM5YnNcyQKtgCPx9IJb2pjv8sJgxCBQqC9kX0"
  """
  def hash_pwd(pass) do
    Crypto.encrypt_sha(pass)
  end

  defp apply_cap(conn, config) do
    resource = get_resource(conn)

    role =
      String.downcase(resource.role)
      |> String.to_atom()

    exception = config[:exception]

    if exception != nil and role == exception do
      conn
    else
      not_exception(conn, config, resource, role)
    end
  end

  defp not_exception(conn, config, resource, role) do
    router = conn.private.phoenix_router
    policy = config[:policy]
    effect = config[:effect] == :allow
    req = Phoenix.Router.route_info(router, conn.method, conn.request_path, conn.host)

    rbac = Rbac.apply_rbac(effect, policy, role, req)
    abac = Abac.apply_abac(req, resource)

    case rbac and abac do
      true -> conn
      _ -> raise Cap.ErrorHandler, "403"
    end
  end

  @doc """
  Get resource.

  ## Example

      Cap.get_resource(conn)
  	  %{id: 1, role: "ROOT"}
  """
  def get_resource(conn) do
    cap = get_session(conn, :cap)

    case cap do
      nil -> %{id: nil, role: "nil"}
      _ -> cap
    end
  end
end