Packages
ash_authentication
5.0.0-rc.4
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/audit_log_resource/transformer.ex
# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd
#
# SPDX-License-Identifier: MIT
defmodule AshAuthentication.AuditLogResource.Transformer do
@moduledoc false
use Spark.Dsl.Transformer
alias Ash.{Resource, Type}
alias AshAuthentication.AuditLogResource.Info
alias Spark.Dsl.Transformer
alias Spark.Error.DslError
import Ash.Expr
import AshAuthentication.Utils
import AshAuthentication.Validations
import AshAuthentication.Validations.Action
import AshAuthentication.Validations.Attribute
@doc false
@impl true
@spec after?(any) :: boolean()
def after?(_), do: false
@doc false
@impl true
@spec before?(any) :: boolean
def before?(Resource.Transformers.CachePrimaryKey), do: true
def before?(Resource.Transformers.DefaultAccept), do: true
def before?(_), do: false
@doc false
@impl true
def transform(dsl) do
with {:ok, dsl} <- maybe_set_domain(dsl, :audit_log),
{:ok, dsl} <- build_and_validate_attributes(dsl),
{:ok, dsl} <- build_and_validate_actions(dsl),
:ok <- validate_primary_key(dsl) do
{:ok, dsl}
end
end
defp validate_primary_key(dsl) do
with {:ok, id_attr} <- Info.audit_log_attributes_id(dsl) do
dsl
|> Resource.Info.attributes()
|> Enum.filter(& &1.primary_key?)
|> Enum.map(& &1.name)
|> case do
[^id_attr] ->
:ok
fields ->
module = Transformer.get_persisted(dsl, :module)
{:error,
DslError.exception(
module: module,
path: [:primary_key],
message: """
The audit log resource must only have `#{inspect(id_attr)}` as it's primary key attribute.
Found: #{inspect(fields)}
"""
)}
end
end
end
defp build_and_validate_attributes(dsl) do
with {:ok, id_attr} <- Info.audit_log_attributes_id(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, id_attr, :uuid_v7,
primary_key?: true,
allow_nil?: false,
writable?: true,
public?: true,
default: &Ash.UUIDv7.generate/0
),
:ok <- validate_id_field(dsl, id_attr),
{:ok, subject_attr} <- Info.audit_log_attributes_subject(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, subject_attr, :string,
allow_nil?: true,
writable?: true,
public?: true
),
:ok <- validate_subject_field(dsl, subject_attr),
{:ok, identity_attr} <- Info.audit_log_attributes_identity(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, identity_attr, :string,
allow_nil?: true,
writable?: true,
public?: true,
sensitive?: true
),
:ok <- validate_identity_field(dsl, identity_attr),
{:ok, client_ip_attr} <- Info.audit_log_attributes_client_ip(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, client_ip_attr, :string,
allow_nil?: true,
writable?: true,
public?: true,
sensitive?: true
),
:ok <- validate_client_ip_field(dsl, client_ip_attr),
{:ok, strategy_attr} <- Info.audit_log_attributes_strategy(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, strategy_attr, :atom,
allow_nil?: false,
writable?: true,
public?: true
),
:ok <- validate_strategy_field(dsl, strategy_attr),
{:ok, audit_log_attr} <- Info.audit_log_attributes_audit_log(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, audit_log_attr, :atom,
allow_nil?: false,
writable?: true,
public?: true
),
:ok <- validate_audit_log_field(dsl, audit_log_attr),
{:ok, logged_at_attr} <- Info.audit_log_attributes_logged_at(dsl),
{:ok, dsl} <-
maybe_build_attribute(
dsl,
logged_at_attr,
:utc_datetime_usec,
allow_nil?: false,
writable?: true,
public?: true,
default: &DateTime.utc_now/0
),
:ok <- validate_logged_at_field(dsl, logged_at_attr),
{:ok, action_name_attr} <- Info.audit_log_attributes_action_name(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, action_name_attr, :atom,
allow_nil?: false,
writable?: true,
public?: true
),
:ok <- validate_action_name_field(dsl, action_name_attr),
{:ok, status_attr} <- Info.audit_log_attributes_status(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, status_attr, :atom,
allow_nil?: false,
writable?: true,
public?: true,
constraints: [one_of: [:success, :failure, :unknown]]
),
:ok <- validate_status_field(dsl, status_attr),
{:ok, extra_data_attr} <- Info.audit_log_attributes_extra_data(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, extra_data_attr, :map,
allow_nil?: false,
writable?: true,
public?: true,
default: %{}
),
:ok <- validate_extra_data_field(dsl, extra_data_attr),
{:ok, resource_attr} <- Info.audit_log_attributes_resource(dsl),
{:ok, dsl} <-
maybe_build_attribute(dsl, resource_attr, :atom,
allow_nil?: false,
writable?: true,
public?: true
),
:ok <- validate_resource_field(dsl, resource_attr) do
{:ok, dsl}
end
end
defp build_and_validate_actions(dsl) do
with {:ok, write_action_name} <- Info.audit_log_write_action_name(dsl),
{:ok, dsl} <-
maybe_build_action(dsl, write_action_name, &build_write_action(&1, write_action_name)),
:ok <- validate_write_action(dsl, write_action_name) do
maybe_build_expunge_actions(dsl)
end
end
defp maybe_build_expunge_actions(dsl) do
case Info.audit_log_log_lifetime(dsl) do
{:ok, :infinity} ->
{:ok, dsl}
_ ->
with {:ok, destroy_action_name} <- Info.audit_log_destroy_action_name(dsl),
{:ok, dsl} <-
maybe_build_action(
dsl,
destroy_action_name,
&build_destroy_action(&1, destroy_action_name)
),
:ok <- validate_destroy_action(dsl, destroy_action_name),
{:ok, read_expired_action_name} <- Info.audit_log_read_expired_action_name(dsl),
{:ok, dsl} <-
maybe_build_action(
dsl,
read_expired_action_name,
&build_read_expired_action(&1, read_expired_action_name)
),
:ok <- validate_read_expired_action(dsl, read_expired_action_name) do
{:ok, dsl}
end
end
end
defp build_write_action(_dsl, action_name) do
Transformer.build_entity(Resource.Dsl, [:actions], :create, name: action_name, accept: :*)
end
defp validate_write_action(dsl, action_name) do
with {:ok, action} <- validate_action_exists(dsl, action_name),
:ok <- validate_field_in_values(action, :type, [:create]) do
case action.accept do
[:*] ->
:ok
:* ->
:ok
fields ->
validate_accepted_fields(dsl, fields)
end
end
end
defp validate_accepted_fields(dsl, fields) do
with {:ok, id} <- Info.audit_log_attributes_id(dsl),
{:ok, subject} <- Info.audit_log_attributes_subject(dsl),
{:ok, identity} <- Info.audit_log_attributes_identity(dsl),
{:ok, client_ip} <- Info.audit_log_attributes_client_ip(dsl),
{:ok, strategy} <- Info.audit_log_attributes_strategy(dsl),
{:ok, audit_log} <- Info.audit_log_attributes_audit_log(dsl),
{:ok, logged_at} <- Info.audit_log_attributes_logged_at(dsl),
{:ok, action_name} <- Info.audit_log_attributes_action_name(dsl),
{:ok, status} <- Info.audit_log_attributes_status(dsl),
{:ok, extra_data} <- Info.audit_log_attributes_extra_data(dsl),
{:ok, resource} <- Info.audit_log_attributes_resource(dsl) do
expected =
MapSet.new([
id,
subject,
identity,
client_ip,
strategy,
audit_log,
logged_at,
action_name,
status,
extra_data,
resource
])
fields = MapSet.new(fields)
if MapSet.subset?(expected, fields) do
:ok
else
{:error,
DslError.exception(
module: Transformer.get_persisted(dsl, :module),
path: [:actions, :create, action_name, :accept],
message: """
Expected the `#{inspect(action_name)}` action to accept at least the required attributes.
Missing: #{inspect(MapSet.difference(expected, fields) |> Enum.to_list())}
"""
)}
end
end
end
defp build_destroy_action(dsl, action_name) do
with %{log_lifetime: days} when is_integer(days) and days >= 0 <-
Info.audit_log_options(dsl),
{:ok, logged_at_attr} <- Info.audit_log_attributes_logged_at(dsl),
{:ok, change} <-
Transformer.build_entity(Resource.Dsl, [:actions, :destroy], :change,
change:
{Resource.Change.Filter, filter: expr(^ref(logged_at_attr) <= ago(^days, :day))}
) do
Transformer.build_entity(Resource.Dsl, [:actions], :destroy,
name: action_name,
changes: [change]
)
else
%{log_lifetime: :infinity} -> {:ok, dsl}
{:error, reason} -> {:error, reason}
end
end
defp validate_destroy_action(dsl, action_name) do
with %{log_lifetime: days} when is_integer(days) and days >= 0 <- Info.audit_log_options(dsl),
{:ok, action} <- validate_action_exists(dsl, action_name),
:ok <- validate_field_in_values(action, :type, [:destroy]) do
validate_action_has_change(action, Resource.Change.Filter)
else
%{log_lifetime: :infinity} -> :ok
{:error, reason} -> {:error, reason}
end
end
defp build_read_expired_action(dsl, action_name) do
with %{log_lifetime: days} when is_integer(days) and days >= 0 <- Info.audit_log_options(dsl),
{:ok, logged_at_attr} <- Info.audit_log_attributes_logged_at(dsl),
{:ok, filter} <-
Transformer.build_entity(Resource.Dsl, [:actions, :read], :filter,
filter: expr(^ref(logged_at_attr) <= ago(^days, :day))
) do
Transformer.build_entity(Resource.Dsl, [:actions], :read,
name: action_name,
filters: [filter]
)
end
end
defp validate_read_expired_action(dsl, action_name) do
with %{log_lifetime: days} when is_integer(days) and days >= 0 <- Info.audit_log_options(dsl),
{:ok, action} <- validate_action_exists(dsl, action_name) do
validate_field_in_values(action, :type, [:read])
else
%{log_lifetime: :infinity} -> :ok
{:error, reason} -> {:error, reason}
end
end
defp validate_id_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.UUIDv7, :uuid_v7]),
:ok <- validate_attribute_option(attribute, resource, :primary_key?, [true]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :public?, [true]) do
validate_attribute_option(attribute, resource, :default, [&Ash.UUIDv7.generate/0])
end
end
defp validate_subject_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.String, :string]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [true]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
defp validate_identity_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.String, :string]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [true]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :public?, [true]) do
validate_attribute_option(attribute, resource, :sensitive?, [true])
end
end
defp validate_client_ip_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.String, :string]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [true]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :public?, [true]) do
validate_attribute_option(attribute, resource, :sensitive?, [true])
end
end
defp validate_strategy_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.Atom, :atom]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
defp validate_audit_log_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.Atom, :atom]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
defp validate_logged_at_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [
Type.UtcDatetimeUsec,
:utc_datetime_usec
]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :public?, [true]) do
validate_attribute_option(attribute, resource, :default, [&DateTime.utc_now/0])
end
end
defp validate_action_name_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.Atom, :atom]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
defp validate_status_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.Atom, :atom]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
defp validate_extra_data_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <-
validate_attribute_option(attribute, resource, :type, [Type.Map, :map]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]),
:ok <- validate_attribute_option(attribute, resource, :public?, [true]) do
validate_attribute_option(attribute, resource, :default, [%{}])
end
end
defp validate_resource_field(dsl, attr) do
with {:ok, resource} <- persisted_option(dsl, :module),
{:ok, attribute} <- find_attribute(dsl, attr),
:ok <- validate_attribute_option(attribute, resource, :type, [Type.Atom, :atom]),
:ok <- validate_attribute_option(attribute, resource, :allow_nil?, [false]),
:ok <- validate_attribute_option(attribute, resource, :writable?, [true]) do
validate_attribute_option(attribute, resource, :public?, [true])
end
end
end