defmodule BallsPDS.Ecto.Schema.ObjectReadAgent do use Ecto.Schema import Ecto.Changeset import Ecto.Query import BallsPDS.Util.ACL, only: [is_valid_acl?: 1] alias BallsPDS.Ecto.Schema.Object alias BallsPDS.Ecto.Schema.Agent alias BallsPDS.Repo @primary_key false schema "object_read_agents" do belongs_to(:object, BallsPDS.Ecto.Schema.Object, foreign_key: :object_id) belongs_to(:agent, BallsPDS.Ecto.Schema.Agent, foreign_key: :agent_id) end def changeset(struct, params \\ %{}) do struct |> cast(params, [ :object_id, :agent_id ]) |> validate_required([ :object_id, :agent_id ]) |> foreign_key_constraint(:object_id) |> foreign_key_constraint(:agent_id) end def delete(object_id, agent_id) when is_integer(object_id) and is_integer(agent_id) do query = from(ora in __MODULE__, where: ora.object_id == ^object_id and ora.agent_id == ^agent_id ) Repo.delete_all(query) end def delete(object_id) when is_binary(object_id) do query = from(ora in __MODULE__, where: ora.object_id == ^object_id ) Repo.delete_all(query) end def insert(object_id, agent_id) when is_integer(object_id) and is_integer(agent_id) do Repo.insert( %__MODULE__{object_id: object_id, agent_id: agent_id}, on_conflict: :nothing ) end def revoke_read(%Object{id: object_id}, acl) when is_binary(acl) do with {:exists, %Agent{id: agent_id}} <- {:exists, Agent.get_by_acl(acl)}, {:delete, {:ok, _}} <- {:delete, delete(object_id, agent_id)} do else {:exists, nil} -> {:error, :no_agent} {:delete, {:error, error}} -> {:error, {:delete, error}} end end # still leaves public. def revoke_all_read(%Object{id: object_id}) do delete(object_id) end def authorize_read(%Object{id: object_id}, acl) when is_binary(acl) do with {:valid_acl, true} <- {:valid_acl, is_valid_acl?(acl)}, {:agent, {:ok, %Agent{id: agent_id, disabled: false}}} <- {:agent, Agent.get_or_create_by_acl(acl)}, {:insert, {:ok, _}} <- {:insert, insert(object_id, agent_id)} do :ok else {:valid_acl, false} -> {:error, :invalid_acl} {:agent, {:error, _} = error} -> error {:agent, {:ok, %{disabled: true}}} -> {:error, :disabled_agent} {:insert, {:error, _} = error} -> error end end def authorize_read(%Object{} = object, acls = []) do errors = Enum.reduce(acls, [], fn acl, errors when is_binary(acl) -> case authorize_read(object, acl) do :ok -> errors {:error, error} -> [{acl, error} | errors] end acl, errors -> [{acl, :invalid_acl} | errors] end) if length(errors == 0) do :ok else {:error, errors} end end def is_authorized_read?(%Object{id: object_id}, acl) when is_binary(acl) do query = from(oa in __MODULE__, join: a in Agent, on: oa.agent_id == a.id, where: a.acl == ^acl and oa.object_id == ^object_id and a.disabled == false ) Repo.exists?(query) end def get_read_agents(%Object{id: object_id}) do query = from(oa in __MODULE__, join: a in Agent, on: oa.agent_id == a.id, where: oa.object_id == ^object_id and a.disabled == false, select: a.acl ) Repo.all(query) end end