# SPDX-FileCopyrightText: 2019 ash contributors # # SPDX-License-Identifier: MIT defmodule Ash.Authorizer do @moduledoc """ The interface for an ash authorizer These will typically be implemented by an extension, but a custom one can be implemented by defining an extension that also adopts this behaviour. Then you can extend a resource with `authorizers: [YourAuthorizer]` """ @type state :: map @type context :: map @callback initial_state( Ash.Resource.t(), Ash.Resource.record(), Ash.Resource.Actions.action(), Ash.Domain.t() ) :: state @callback strict_check_context(state) :: [atom] @callback strict_check(state, context) :: {:authorized, state} | {:continue, state} | {:filter, Keyword.t()} | {:filter, Keyword.t(), state} | {:filter_and_continue, Keyword.t(), state} | {:error, term} @callback alter_filter(filter :: Ash.Filter.t(), state, context) :: {:ok, Ash.Filter.t()} | {:error, Ash.Error.t()} @callback add_calculations(Ash.Query.t() | Ash.Changeset.t(), state, context) :: {:ok, Ash.Query.t() | Ash.Changeset.t(), state} | {:error, Ash.Error.t()} @callback alter_results(state, list(Ash.Resource.record()), context) :: {:ok, list(Ash.Resource.record())} | {:error, Ash.Error.t()} @callback check_context(state) :: [atom] @callback check(state, context) :: :authorized | {:data, list(Ash.Resource.record())} | {:error, :forbidden, state} | {:error, Ash.Error.t()} @callback exception(atom, state) :: no_return @optional_callbacks [exception: 2, add_calculations: 3, alter_results: 3, alter_filter: 3] defmacro __using__(_) do quote do @behaviour Ash.Authorizer end end def initial_state(module, actor, resource, action, domain) do module.initial_state(actor, resource, action, domain) end def exception(module, reason, state) do if function_exported?(module, :exception, 2) do module.exception(reason, state) else if reason == :must_pass_strict_check do Ash.Error.Forbidden.MustPassStrictCheck.exception([]) else Ash.Error.Forbidden.exception([]) end end end def strict_check_context(module, state) do Enum.uniq(module.strict_check_context(state) ++ [:query, :changeset]) end def strict_check(module, state, context) do module.strict_check(state, context) end def add_calculations(module, query_or_changeset, state, context) do if function_exported?(module, :add_calculations, 3) do module.add_calculations(query_or_changeset, state, context) else {:ok, query_or_changeset, state} end end def alter_results(module, state, records, context) do if function_exported?(module, :alter_results, 3) do module.alter_results(state, records, context) else {:ok, records} end end def alter_filter(module, state, filter, context) do if function_exported?(module, :alter_filter, 3) do module.alter_filter(filter, state, context) else {:ok, filter} end end def alter_sort(module, state, sort, context) do if function_exported?(module, :alter_sort, 3) do module.alter_sort(sort, state, context) else {:ok, sort} end end def check_context(module, state) do module.check_context(state) end def check(module, state, context) do module.check(state, context) end end