Packages
ash_authentication
3.11.15
5.0.0-rc.12
5.0.0-rc.11
5.0.0-rc.10
5.0.0-rc.9
5.0.0-rc.8
5.0.0-rc.7
5.0.0-rc.6
5.0.0-rc.5
5.0.0-rc.4
5.0.0-rc.3
5.0.0-rc.2
5.0.0-rc.1
5.0.0-rc.0
4.14.1
4.14.0
4.13.7
4.13.6
4.13.5
4.13.4
4.13.3
4.13.2
retired
4.13.1
retired
4.13.0
4.12.0
4.11.0
4.10.0
4.9.9
4.9.8
4.9.7
4.9.6
4.9.5
4.9.4
4.9.3
4.9.2
4.9.1
4.9.0
4.8.7
4.8.6
4.8.5
4.8.3
4.8.2
4.8.1
4.8.0
4.7.6
4.7.5
4.7.4
4.7.3
4.7.2
4.7.1
4.7.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.12
4.3.11
4.3.10
4.3.9
4.3.8
4.3.7
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
4.0.0-rc.6
4.0.0-rc.5
4.0.0-rc.3
4.0.0-rc.2
4.0.0-rc.1
4.0.0-rc.0
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.0
3.7.9
3.7.8
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.1
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.3.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.0
3.0.3
Authentication extension for the Ash Framework.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash_authentication/transformer.ex
defmodule AshAuthentication.Transformer do
@moduledoc """
The Authentication transformer
Sets up non-provider-specific configuration for authenticated resources.
"""
use Spark.Dsl.Transformer
alias Ash.Resource
alias AshAuthentication.{Info, Strategy}
alias Spark.{Dsl.Transformer, Error.DslError}
import AshAuthentication.Utils
import AshAuthentication.Validations
import AshAuthentication.Validations.Action
@doc false
@impl true
@spec after?(any) :: boolean()
def after?(Resource.Transformers.ValidatePrimaryActions), do: true
def after?(_), do: false
@doc false
@impl true
@spec before?(any) :: boolean
def before?(Resource.Transformers.DefaultAccept), do: true
def before?(_), do: false
@doc false
@impl true
@spec transform(map) ::
:ok | {:ok, map} | {:error, term} | {:warn, map, String.t() | [String.t()]} | :halt
def transform(dsl_state) do
with :ok <- validate_at_least_one_strategy(dsl_state),
:ok <- validate_unique_strategy_names(dsl_state),
:ok <- validate_unique_add_on_names(dsl_state),
{:ok, dsl_state} <- maybe_transform_token_lifetime(dsl_state),
{:ok, get_by_subject_action_name} <-
Info.authentication_get_by_subject_action_name(dsl_state),
{:ok, dsl_state} <-
maybe_build_action(
dsl_state,
get_by_subject_action_name,
&build_get_by_subject_action/1
),
:ok <- validate_read_action(dsl_state, get_by_subject_action_name),
subject_name <- find_or_generate_subject_name(dsl_state),
current_user when is_atom(current_user) <- ensure_current_user_atom_exists(subject_name) do
dsl_state =
dsl_state
|> Transformer.set_option([:authentication], :subject_name, subject_name)
{:ok, dsl_state}
end
end
defp maybe_transform_token_lifetime(dsl_state) do
case Info.authentication_tokens_token_lifetime(dsl_state) do
{:ok, {_ttl, unit}} when unit in ~w[days hours minutes seconds]a ->
{:ok, dsl_state}
{:ok, ttl} when is_integer(ttl) and ttl > 0 ->
{:ok,
Transformer.set_option(
dsl_state,
[:authentication, :tokens],
:token_lifetime,
{ttl, :hours}
)}
_ ->
{:error,
DslError.exception(
path: [:authentication, :tokens],
message: "Invalid token lifetime"
)}
end
end
defp build_get_by_subject_action(dsl_state) do
with {:ok, get_by_subject_action_name} <-
Info.authentication_get_by_subject_action_name(dsl_state) do
Transformer.build_entity(Resource.Dsl, [:actions], :read,
name: get_by_subject_action_name,
get?: true
)
end
end
# sobelow_skip ["DOS.StringToAtom"]
defp find_or_generate_subject_name(dsl_state) do
with nil <- Transformer.get_option(dsl_state, [:authentication], :subject_name),
nil <- Transformer.get_option(dsl_state, [:resource], :short_name) do
# We have to do this because the resource has not yet been compiled, so we can't call `default_short_name/0`.
dsl_state
|> Transformer.get_persisted(:module)
|> Module.split()
|> List.last()
|> Macro.underscore()
|> String.to_atom()
end
end
# sobelow_skip ["DOS.StringToAtom"]
defp ensure_current_user_atom_exists(subject_name),
do: String.to_atom("current_#{subject_name}")
defp validate_at_least_one_strategy(dsl_state) do
ok? =
dsl_state
|> Transformer.get_entities([:authentication, :strategies])
|> Enum.any?()
if ok?,
do: :ok,
else:
{:error,
DslError.exception(
path: [:authentication, :strategies],
message: "Expected at least one authentication strategy"
)}
end
defp validate_read_action(dsl_state, action_name) do
with {:ok, action} <- validate_action_exists(dsl_state, action_name),
:ok <- validate_field_in_values(action, :type, [:read]) do
:ok
else
_ ->
{:error,
DslError.exception(
path: [:actions],
message: "Expected resource to have either read action named `#{action_name}`"
)}
end
end
defp validate_unique_add_on_names(dsl_state) do
dsl_state
|> Transformer.get_entities([:authentication, :add_ons])
|> Enum.map(&Strategy.name/1)
|> validate_unique("add on")
end
defp validate_unique_strategy_names(dsl_state) do
dsl_state
|> Transformer.get_entities([:authentication, :strategies])
|> Enum.map(&Strategy.name/1)
|> validate_unique("strategy")
end
defp validate_unique(strategy_names, descriptor) do
duplicates =
strategy_names
|> Enum.frequencies()
|> Enum.reject(&(elem(&1, 1) == 1))
if Enum.any?(duplicates) do
errors =
duplicates
|> Enum.map_join("\n", fn
{name, 2} -> " * #{descriptor} `#{inspect(name)}` is repeated twice."
{name, n} -> " * #{descriptor} `#{inspect(name)}` is repeated #{n} times."
end)
{:error,
DslError.exception(
path: [:authentication, :strategies],
message: "Strategy names must be unique.\n\n#{errors}"
)}
else
:ok
end
end
end