From 16c8a8a8487ea28200d941bc2eed1aa104ba1083 Mon Sep 17 00:00:00 2001 From: Nelson Kopliku Date: Tue, 2 Jul 2024 16:11:41 +0200 Subject: [PATCH] wip --- .../activity_logging/activity_logger.ex | 38 +++++- .../activity_logging/activity_parser.ex | 20 +++ .../activity_logging/phoenix_conn_parser.ex | 127 ++++++++++++++++++ lib/trento/activity_logging/registry.ex | 65 +++++++++ .../logger/adapter/persistent_logger.ex | 12 ++ 5 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 lib/trento/activity_logging/activity_parser.ex create mode 100644 lib/trento/activity_logging/phoenix_conn_parser.ex create mode 100644 lib/trento/activity_logging/registry.ex create mode 100644 lib/trento/infrastructure/activity_log/logger/adapter/persistent_logger.ex diff --git a/lib/trento/activity_logging/activity_logger.ex b/lib/trento/activity_logging/activity_logger.ex index be2099c8f7..df7d937b35 100644 --- a/lib/trento/activity_logging/activity_logger.ex +++ b/lib/trento/activity_logging/activity_logger.ex @@ -3,9 +3,41 @@ defmodule Trento.ActivityLog.ActivityLogger do ActivityLogger entry point """ - @callback log_activity(context :: any()) :: :ok + alias Trento.ActivityLog.ActivityLog + alias Trento.ActivityLogging.Registry - def log_activity(context), do: adapter().log_activity(context) + @callback log_activity(activity_context :: any()) :: :ok - defp adapter, do: Application.fetch_env!(:trento, __MODULE__)[:adapter] + def log_activity(activity_context) do + activity_parser = get_activity_parser(activity_context) + + detected_activity = activity_parser.detect_activity(activity_context) + + if Registry.interested?(detected_activity, activity_context) do + %ActivityLog{} + |> ActivityLog.changeset(%{ + type: get_activity_type(detected_activity), + actor: get_actor(activity_parser, detected_activity, activity_context), + metadata: get_metadata(activity_parser, detected_activity, activity_context) + }) + |> Trento.Repo.insert() + end + + :ok + end + + defp get_activity_parser(%Plug.Conn{}), do: Trento.ActivityLogging.Logger.PhoenixConnParser + # defp get_logger(%Pipeline{}), do: Trento.ActivityLogging.Logger.CommandedRecongnizer + + defp get_activity_type(detected_activity), + do: + detected_activity + |> Registry.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) end diff --git a/lib/trento/activity_logging/activity_parser.ex b/lib/trento/activity_logging/activity_parser.ex new file mode 100644 index 0000000000..cd0bba2874 --- /dev/null +++ b/lib/trento/activity_logging/activity_parser.ex @@ -0,0 +1,20 @@ +defmodule Trento.ActivityLogging.ActivityParser do + @moduledoc """ + Behavior for activity parsers. + It extracts the activity relevant information from the context. + """ + + alias Trento.ActivityLogging.Registry + + @callback detect_activity(activity_context :: any()) :: Registry.logged_activity() | nil + + @callback get_activity_actor( + activity :: Registry.logged_activity(), + activity_context :: any() + ) :: any() + + @callback get_activity_metadata( + activity :: Registry.logged_activity(), + activity_context :: any() + ) :: map() +end diff --git a/lib/trento/activity_logging/phoenix_conn_parser.ex b/lib/trento/activity_logging/phoenix_conn_parser.ex new file mode 100644 index 0000000000..989ff561f8 --- /dev/null +++ b/lib/trento/activity_logging/phoenix_conn_parser.ex @@ -0,0 +1,127 @@ +defmodule Trento.ActivityLogging.Logger.PhoenixConnParser do + @moduledoc """ + Phoenix connection activity parser + """ + + alias Phoenix.Controller + + alias Trento.Users.User + + require Trento.ActivityLogging.Registry, as: Registry + + @behaviour Trento.ActivityLogging.ActivityParser + + def detect_activity(%Plug.Conn{} = conn) do + {Controller.controller_module(conn), Controller.action_name(conn)} + rescue + _ -> nil + end + + def get_activity_actor(Registry.login_attempt(), %Plug.Conn{body_params: request_payload}), + do: Map.get(request_payload, "username", "no_username") + + def get_activity_actor(_, %Plug.Conn{} = conn) do + case Pow.Plug.current_user(conn) do + %User{username: username} -> username + _ -> "system" + end + end + + def get_activity_metadata( + Registry.login_attempt() = action, + %Plug.Conn{ + assigns: %{ + reason: reason + }, + status: 401 + } = conn + ) do + %{ + username: get_activity_actor(action, conn), + reason: reason + } + end + + def get_activity_metadata( + Registry.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 + + def get_activity_metadata( + Registry.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 + + def get_activity_metadata( + Registry.api_key_generation(), + %Plug.Conn{ + body_params: request_body + } + ) do + request_body + end + + def get_activity_metadata( + action, + %Plug.Conn{ + body_params: request_body + } + ) + when action in [Registry.saving_suma_settings(), Registry.changing_suma_settings()] do + request_body + |> redact(:password) + |> redact(:ca_cert) + end + + def get_activity_metadata( + action, + %Plug.Conn{ + body_params: request_body + } + ) + when action in [ + Registry.user_creation(), + Registry.user_modification(), + Registry.profile_update() + ] do + request_body + |> redact(:password) + |> redact(:current_password) + |> redact(:password_confirmation) + end + + 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 diff --git a/lib/trento/activity_logging/registry.ex b/lib/trento/activity_logging/registry.ex new file mode 100644 index 0000000000..a50f355aa8 --- /dev/null +++ b/lib/trento/activity_logging/registry.ex @@ -0,0 +1,65 @@ +defmodule Trento.ActivityLogging.Registry do + @moduledoc """ + Activity logging registry + """ + + @type logged_activity :: {controller :: module(), action :: atom()} + + @login_attempt {TrentoWeb.SessionController, :create} + @api_key_generation {TrentoWeb.V1.SettingsController, :update_api_key_settings} + @saving_suma_settings {TrentoWeb.V1.SUMACredentialsController, :create} + @changing_suma_settings {TrentoWeb.V1.SUMACredentialsController, :update} + @clearing_suma_settings {TrentoWeb.V1.SUMACredentialsController, :delete} + @tagging {TrentoWeb.V1.TagsController, :add_tag} + @untagging {TrentoWeb.V1.TagsController, :remove_tag} + @user_creation {TrentoWeb.V1.UsersController, :create} + @user_modification {TrentoWeb.V1.UsersController, :update} + @user_deletion {TrentoWeb.V1.UsersController, :delete} + @profile_update {TrentoWeb.V1.ProfileController, :update} + # @cluster_checks_selection {TrentoWeb.V1.ClusterController, :select_checks} # <-- this is event sourced + @cluster_checks_execution_request {TrentoWeb.V1.ClusterController, :request_checks_execution} + + @action_registry %{ + @login_attempt => {:login_attempt, :any}, + @tagging => {:resource_tagging, 201}, + @untagging => {:resource_untagging, 204}, + @api_key_generation => {:api_key_generation, 200}, + @saving_suma_settings => {:saving_suma_settings, 201}, + @changing_suma_settings => {:changing_suma_settings, 200}, + @clearing_suma_settings => {:clearing_suma_settings, 204}, + @user_creation => {:user_creation, 201}, + @user_modification => {:user_modification, 200}, + @user_deletion => {:user_deletion, 204}, + @profile_update => {:profile_update, 200}, + @cluster_checks_execution_request => {:cluster_checks_selection, 202} + } + + Enum.each(@action_registry, fn {action, {action_type, _}} -> + defmacro unquote(action_type)(), do: unquote(action) + end) + + def interested?(activity, %Plug.Conn{status: status}), + do: + Map.has_key?(@action_registry, activity) && + @action_registry + |> Map.fetch!(activity) + |> interesting_status?(status) + + def interested?(_, _), do: false + + @spec get_activity_type(logged_activity()) :: atom() + def get_activity_type(activity) do + @action_registry + |> Map.fetch!(activity) + |> elem(0) + end + + @spec interesting_status?( + request_activity :: {activity_type :: atom(), relevant_status :: integer() | :any}, + detected_status :: integer() + ) :: + boolean() + defp interesting_status?({_, :any}, _), do: true + defp interesting_status?({_, status}, status), do: true + defp interesting_status?(_, _), do: false +end diff --git a/lib/trento/infrastructure/activity_log/logger/adapter/persistent_logger.ex b/lib/trento/infrastructure/activity_log/logger/adapter/persistent_logger.ex new file mode 100644 index 0000000000..2cf0db0220 --- /dev/null +++ b/lib/trento/infrastructure/activity_log/logger/adapter/persistent_logger.ex @@ -0,0 +1,12 @@ +defmodule Trento.Infrastructure.ActivityLog.Logger.PersistentLogger do + @moduledoc """ + Persistent Activity Logger Adapter + """ + + @behaviour Trento.ActivityLog.ActivityLogger + + @impl true + def log_activity(_activity) do + :ok + end +end