Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonkopliku committed Jul 3, 2024
1 parent eefeb17 commit 5f2113b
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 8 deletions.
3 changes: 2 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ config :trento, Trento.Infrastructure.SoftwareUpdates.SumaApi,
executor: Trento.Infrastructure.SoftwareUpdates.Suma.HttpExecutor

config :trento, Trento.ActivityLog.ActivityLogger,
adapter: Trento.Infrastructure.ActivityLog.Logger.NoopLogger
adapter: Trento.ActivityLogging.Logger.PersistentLogger,
writer: Trento.Infrastructure.ActivityLog.Logger.DatabasetWriter

config :bodyguard,
# The second element of the {:error, reason} tuple returned on auth failure
Expand Down
16 changes: 9 additions & 7 deletions lib/trento/activity_logging/activity_catalog.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Trento.ActivityLogging.ActivityCatalog do
Activity logging catalog
"""

@type logged_activity :: {controller :: module(), action :: atom()}
@type logged_activity :: {controller :: module(), activity :: atom()}

@login_attempt {TrentoWeb.SessionController, :create}
@api_key_generation {TrentoWeb.V1.SettingsController, :update_api_key_settings}
Expand All @@ -19,7 +19,7 @@ defmodule Trento.ActivityLogging.ActivityCatalog do
# @cluster_checks_selection {TrentoWeb.V1.ClusterController, :select_checks} # <-- this is **also** event sourced
@cluster_checks_execution_request {TrentoWeb.V1.ClusterController, :request_checks_execution}

@action_registry %{
@activity_catalog %{
@login_attempt => {:login_attempt, :any},
@tagging => {:resource_tagging, 201},
@untagging => {:resource_untagging, 204},
Expand All @@ -34,22 +34,24 @@ defmodule Trento.ActivityLogging.ActivityCatalog do
@cluster_checks_execution_request => {:cluster_checks_execution_request, 202}
}

Enum.each(@action_registry, fn {action, {action_type, _}} ->
defmacro unquote(action_type)(), do: unquote(action)
Enum.each(@activity_catalog, fn {activity, {activity_type, _}} ->
defmacro unquote(activity_type)(), do: unquote(activity)
end)

def activity_catalog, do: Enum.map(@activity_catalog, fn {activity, _} -> activity end)

def interested?(activity, %Plug.Conn{status: status}),
do:
Map.has_key?(@action_registry, activity) &&
@action_registry
Map.has_key?(@activity_catalog, activity) &&
@activity_catalog
|> Map.fetch!(activity)
|> interesting_occurrence?(status)

def interested?(_, _), do: false

@spec get_activity_type(logged_activity()) :: atom() | nil
def get_activity_type(activity) do
case Map.fetch(@action_registry, activity) do
case Map.fetch(@activity_catalog, activity) do
{:ok, {activity_type, _}} -> activity_type
:error -> nil
end
Expand Down
17 changes: 17 additions & 0 deletions lib/trento/activity_logging/logger/activity_log_writer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule Trento.ActivityLogging.Logger.ActivityLogWriter do
@moduledoc """
Activity Log Writer behaviour
"""

@type log_entry :: %{
type: String.t(),
actor: String.t(),
metadata: map()
}

@callback write_log(log_entry()) :: {:ok, any()} | {:error, any()}

def write_log(log_entry), do: adapter().write_log(log_entry)

defp adapter, do: Application.fetch_env!(:trento, Trento.ActivityLog.ActivityLogger)[:writer]
end
62 changes: 62 additions & 0 deletions lib/trento/activity_logging/logger/persistent_logger.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule Trento.ActivityLogging.Logger.PersistentLogger do
@moduledoc """
Activity Logger that persists interesting activities
"""

alias Trento.ActivityLogging.ActivityCatalog
alias Trento.ActivityLogging.Logger.ActivityLogWriter

require Logger

@behaviour Trento.ActivityLog.ActivityLogger

@impl true
def log_activity(activity_context) do
with {:ok, activity_parser} <- get_activity_parser(activity_context),
detected_activity <- detect_activity(activity_parser, activity_context),
true <- ActivityCatalog.interested?(detected_activity, activity_context) do
write_log(%{
type: get_activity_type(detected_activity),
actor: get_actor(activity_parser, detected_activity, activity_context),
metadata: get_metadata(activity_parser, detected_activity, activity_context)
})
end

:ok
end

defp get_activity_parser(%Plug.Conn{}),
do: {:ok, Trento.ActivityLogging.Logger.Parser.PhoenixConnParser}

defp get_activity_parser(_), do: {:error, :unsupported_activity}

# defp get_activity_parser(%Commanded.Middleware.Pipeline{}),
# do: {:ok, Trento.ActivityLogging.Logger.Parser.CommandedParser}

defp detect_activity(activity_parser, activity_context),
do: activity_parser.detect_activity(activity_context)

defp get_activity_type(detected_activity),
do:
detected_activity
|> ActivityCatalog.get_activity_type()
|> Atom.to_string()

defp get_actor(activity_parser, detected_activity, activity_context),
do: activity_parser.get_activity_actor(detected_activity, activity_context)

defp get_metadata(activity_parser, detected_activity, activity_context),
do: activity_parser.get_activity_metadata(detected_activity, activity_context)

defp write_log(%{type: activity_type} = entry) do
case ActivityLogWriter.write_log(entry) do
{:ok, _} ->
Logger.info("Logged activity: #{activity_type}")

{:error, reason} ->
Logger.error(
"An error occurred while logging activity: #{activity_type}. Reason: #{inspect(reason)}"
)
end
end
end
140 changes: 140 additions & 0 deletions lib/trento/activity_logging/parser/phoenix_conn_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
defmodule Trento.ActivityLogging.Logger.Parser.PhoenixConnParser do
@moduledoc """
Phoenix connection activity parser
"""

alias Phoenix.Controller

alias Trento.Users.User

require Trento.ActivityLogging.ActivityCatalog, as: ActivityCatalog

@behaviour Trento.ActivityLogging.Parser.ActivityParser

@impl true
def detect_activity(%Plug.Conn{} = conn) do
{Controller.controller_module(conn), Controller.action_name(conn)}
rescue
_ -> nil
end

@impl true
def get_activity_actor(ActivityCatalog.login_attempt(), %Plug.Conn{body_params: request_payload}),
do: Map.get(request_payload, "username", "no_username")

@impl true
def get_activity_actor(_, %Plug.Conn{} = conn) do
case Pow.Plug.current_user(conn) do
%User{username: username} -> username
_ -> "system"
end
end

@impl true
def get_activity_metadata(
ActivityCatalog.login_attempt() = action,
%Plug.Conn{
assigns: %{
reason: reason
},
status: 401
} = conn
) do
%{
username: get_activity_actor(action, conn),
reason: reason
}
end

@impl true
def get_activity_metadata(
ActivityCatalog.resource_tagging(),
%Plug.Conn{
params: %{id: resource_id},
assigns: %{
resource_type: resource_type
},
body_params: %{value: added_tag}
}
) do
%{
resource_id: resource_id,
resource_type: resource_type,
added_tag: added_tag
}
end

@impl true
def get_activity_metadata(
ActivityCatalog.resource_untagging(),
%Plug.Conn{
params: %{
id: resource_id,
value: removed_tag
},
assigns: %{
resource_type: resource_type
}
}
) do
%{
resource_id: resource_id,
resource_type: resource_type,
removed_tag: removed_tag
}
end

@impl true
def get_activity_metadata(
ActivityCatalog.api_key_generation(),
%Plug.Conn{
body_params: request_body
}
) do
request_body
end

@impl true
def get_activity_metadata(
action,
%Plug.Conn{
body_params: request_body
}
)
when action in [
ActivityCatalog.saving_suma_settings(),
ActivityCatalog.changing_suma_settings()
] do
request_body
|> redact(:password)
|> redact(:ca_cert)
end

@impl true
def get_activity_metadata(
action,
%Plug.Conn{
body_params: request_body
}
)
when action in [
ActivityCatalog.user_creation(),
ActivityCatalog.user_modification(),
ActivityCatalog.profile_update()
] do
request_body
|> redact(:password)
|> redact(:current_password)
|> redact(:password_confirmation)
end

@impl true
def get_activity_metadata(_, _), do: %{}

defp redact(request_body, key) do
case Map.has_key?(request_body, key) && Map.fetch!(request_body, key) != nil do
true -> Map.update!(request_body, key, fn _ -> "REDACTED" end)
false -> request_body
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Trento.Infrastructure.ActivityLog.Logger.DatabasetWriter do
@moduledoc """
Persistent Activity Log Writer Adapter
"""

alias Trento.ActivityLog.ActivityLog

@behaviour Trento.ActivityLogging.Logger.ActivityLogWriter

@impl true
def write_log(log_entry),
do:
%ActivityLog{}
|> ActivityLog.changeset(log_entry)
|> Trento.Repo.insert()
end
Loading

0 comments on commit 5f2113b

Please sign in to comment.