Current section
Files
Jump to
Current section
Files
lib/middleware/simple_or_policy.ex
defmodule Sucrose.Middleware.SimpleOrPolicy do
alias Absinthe.Resolution
require Logger
@behaviour Absinthe.Middleware
@moduledoc """
This is a simple OR policy handler that takes a very simple approach to absinthe resolution handling
The basis for all of the handlers is to have a common response type:
```
true, false, :ok, :error, {:ok, _}, {:error, message}
```
This will loop through until we have at least one claim that matches.
Think of this one as an or logic.
For posts a context with the claims of:
```
[:author, :owner, :reader]
```
For a mutation of deleting you might be ok with an `:author` or an `:owner`
coming through and being able to delete a post.
This Policy will stop the first chance it gets with a `true` condition.
Another helper aspect of this will allow for a pattern of `:error_out` which means
that if we ever reach this, we stop the or logic and hard escape out.
However for this to work you MUST put this as your first because otherwise with the or
logic it will exit the first `:ok` or `true` it gets.
"""
@error_message :no_proper_resolution_or_config
def call(resolution = %{context: %{claims: claims}}, %{handler: handler}) do
check = simple_resolution(resolution)
response = claims_check(check, claims, handler)
# IO.puts("\n\nBEGIN")
# IO.inspect(response)
# IO.inspect("For: #{inspect(Map.take(check, [:child, :parent]))}")
# IO.inspect("For Claims: #{inspect(claims)}")
# IO.puts("END\n\n")
case response do
{:ok, _} -> resolution
:ok -> resolution
true -> resolution
{:error, msg} -> handle_error(check, msg)
_ -> handle_error(check)
end
rescue
_ ->
{:error, @error_message}
end
def call(_, _) do
{:error, @error_message}
end
def claims_check(check, claims, handler, last \\ :error)
def claims_check(_check, [], _handler, last), do: last
def claims_check(check, [head | rest], handler, last) do
new_check = Map.put(check, :claim, head)
current_claim_check =
case new_check do
%{from: :mutation} -> handler.can_mutate?(new_check)
_ -> handler.can_query?(new_check)
end
# IO.inspect("For: #{inspect(Map.take(new_check, [:child, :parent, :claim]))}")
# IO.inspect(current_claim_check)
case current_claim_check do
# immediate exits
:ok -> :ok
true -> :ok
:error_out -> :error
{:error_out, msg} -> {:error, msg}
# Continue on for anything else
_ -> claims_check(check, rest, handler, last)
end
end
@doc """
This is the default error handler to put when we want to deny something.
"""
def handle_error(check, message \\ "Unauthorized") do
Logger.warn(fn ->
"Erroring out for: #{inspect(Map.take(check, [:child, :parent, :claim]))}"
end)
Resolution.put_result(check.resolution, {:error, message})
end
@spec simple_resolution(map()) :: %{
child: :atom,
parent: :atom,
resolution: map()
}
def simple_resolution(resolution) do
%{
child: resolution.definition.schema_node.identifier,
parent: resolution.parent_type.identifier,
resolution: resolution
}
end
end