Skip to content
This repository has been archived by the owner on Jun 30, 2021. It is now read-only.

Complete Activity Log System #560

Merged
merged 28 commits into from Dec 14, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d822f73
Audit for AccountUser
Nov 21, 2018
a367001
Add Auditable to all schemas
Nov 21, 2018
dc0e60a
Merge branch 'master' into 463-complete-audit-system
Dec 3, 2018
7195f33
Move utils modules from EWalletConfig to Utils sub app
Dec 3, 2018
b303f0c
Implement ActivityLogger sub application
Dec 3, 2018
4d33658
Integrate ActivityLogger in EWalletDB
Dec 3, 2018
884d659
Add missing dependency to local ledger sub app
Dec 3, 2018
fb46496
Integrate ActivityLogger in EWallet sub app
Dec 3, 2018
dc2fc57
Integrate ActivityLogger in EWalletAPI sub app
Dec 3, 2018
944846a
Integrate ActivityLogger in AdminAPI
Dec 3, 2018
2e19f73
Add ActivityLogger logic to EWalletConfig
Dec 3, 2018
af80f74
Updated seeds to work with ActivityLogger
Dec 3, 2018
6bc3a57
Remove unneeded originator from EWalletDB schemas
Dec 3, 2018
05142e9
Merge branch 'master' into 463-complete-audit-system
Dec 3, 2018
88630ae
Add account test and handle encrypted_data properly
Dec 6, 2018
6465951
Add proper encryption and prevent_save fields to activity logs
Dec 6, 2018
43e244d
Add activity logs tests for accounts, users and invites
Dec 6, 2018
5e6e77d
Merge master
Dec 13, 2018
534348c
Add tests for log generation in controllers
Dec 13, 2018
a535a8f
Fix PR comments
Dec 13, 2018
41676d5
Merge branch 'master' into 463-complete-audit-system
Dec 13, 2018
cb87f6a
Remove EWalletConfig module dependency from ActivityLogger
Dec 14, 2018
b075b1e
Fix wrong table name in migration
Dec 14, 2018
219bae5
Merge remote-tracking branch 'origin/master' into 463-complete-audit-…
Dec 14, 2018
c967f15
Rename indexes and pkey in migration
Dec 14, 2018
6f597bc
Add more tests for ActivityLog.insert/3
Dec 14, 2018
1d2d7c9
Add a test with both encrypted and prevent_saving
Dec 14, 2018
8b899f7
Merge remote-tracking branch 'origin/master' into 463-complete-audit-…
Dec 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 91 additions & 0 deletions apps/activity_logger/test/support/activity_logger_test_helper.ex
@@ -0,0 +1,91 @@
defmodule ActivityLogger.ActivityLoggerTestHelper do
@moduledoc """
Contains helper methods to make testing activity logging easier
"""
use ExUnit.CaseTemplate
import Ecto.Query
alias ActivityLogger.{ActivityLog, System, Repo}
alias EWalletConfig.{Setting, StoredSetting}

def assert_activity_log(
log,
action: action,
originator: originator,
target: %Setting{} = target,
changes: changes,
encrypted_changes: encrypted_changes
) do
assert_activity_log(
log,
action: action,
originator: originator,
target: %StoredSetting{uuid: target.uuid},
changes: changes,
encrypted_changes: encrypted_changes
)
end

def assert_activity_log(
log,
action: action,
originator: :system,
target: target,
changes: changes,
encrypted_changes: encrypted_changes
) do
assert_activity_log(
log,
action: action,
originator: %System{},
target: target,
changes: changes,
encrypted_changes: encrypted_changes
)
end

def assert_activity_log(
log,
action: action,
originator: originator,
target: target,
changes: changes,
encrypted_changes: encrypted_changes
) do
assert log.action == action
assert log.inserted_at != nil
assert log.originator_type == ActivityLog.get_type(originator.__struct__)
assert log.originator_uuid == originator.uuid
assert log.target_type == ActivityLog.get_type(target.__struct__)
assert log.target_uuid == target.uuid
assert log.target_changes == changes
assert log.target_encrypted_changes == encrypted_changes
end

def assert_simple_activity_log(
log,
action: action,
originator_type: o_type,
target_type: t_type
) do
assert log.action == action
assert log.inserted_at != nil
assert log.originator_type == o_type
assert log.target_type == t_type
end

def get_all_activity_logs(schema) do
type = ActivityLog.get_type(schema.__struct__)

ActivityLog
|> order_by(asc: :inserted_at)
|> where(target_type: ^type)
|> Repo.all()
end

def get_all_activity_logs_since(since) do
ActivityLog
|> order_by(asc: :inserted_at)
|> where([a], a.inserted_at > ^since)
|> Repo.all()
end
end
Expand Up @@ -16,7 +16,8 @@ defmodule AdminAPI.V1.AdminAuthController do
conn <- AdminUserAuthenticator.authenticate(conn, attrs["email"], attrs["password"]),
true <- conn.assigns.authenticated || {:error, :invalid_login_credentials},
true <- User.get_status(conn.assigns.admin_user) == :active || {:error, :invite_pending},
{:ok, auth_token} = AuthToken.generate(conn.assigns.admin_user, :admin_api),
originator <- Originator.extract(conn.assigns),
{:ok, auth_token} = AuthToken.generate(conn.assigns.admin_user, :admin_api, originator),
{:ok, auth_token} <- Orchestrator.one(auth_token, AuthTokenOverlay, attrs) do
render_token(conn, auth_token)
else
Expand Down
Expand Up @@ -66,6 +66,7 @@ defmodule AdminAPI.V1.RoleController do
def update(conn, %{"id" => id} = attrs) do
with :ok <- permit(:update, conn.assigns, id),
%Role{} = original <- Role.get(id) || {:error, :role_id_not_found},
attrs <- Originator.set_in_attrs(attrs, conn.assigns),
{:ok, updated} <- Role.update(original, attrs),
{:ok, updated} <- Orchestrator.one(updated, RoleOverlay, attrs) do
render(conn, :role, %{role: updated})
Expand Down
Expand Up @@ -11,7 +11,8 @@ defmodule AdminAPI.V1.UserAuthController do
def login(conn, attrs) do
with {:ok, %User{} = user} <- UserFetcher.fetch(attrs),
true <- User.enabled?(user) || {:error, :user_disabled},
{:ok, token} = AuthToken.generate(user, :ewallet_api),
originator <- Originator.extract(conn.assigns),
{:ok, token} = AuthToken.generate(user, :ewallet_api, originator),
{:ok, token} = Orchestrator.one(token, AuthTokenOverlay, attrs) do
render(conn, :auth_token, %{auth_token: token})
else
Expand Down
Expand Up @@ -256,6 +256,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do
test "generates an activity log" do
user = get_test_admin()
parent = User.get_account(user)
timestamp = DateTime.utc_now()

request_data = %{
parent_id: parent.id,
Expand All @@ -269,24 +270,41 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do
assert response["success"] == true

account = Account.get(response["data"]["id"])
log = get_last_activity_log(account)

assert log.action == "insert"
assert log.inserted_at != nil
assert log.originator_type == "user"
assert log.originator_uuid == user.uuid
assert log.target_type == "account"
assert log.target_uuid == account.uuid

assert log.target_changes == %{
"metadata" => %{"something" => "interesting"},
"name" => "A test account",
"parent_uuid" => parent.uuid
}

assert log.target_encrypted_changes == %{
"encrypted_metadata" => %{"something" => "secret"}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:mindblown:

logs = get_all_activity_logs_since(timestamp)
assert Enum.count(logs) == 3

logs
|> Enum.at(0)
|> assert_simple_activity_log(
action: "insert",
originator_type: "account",
target_type: "wallet"
)

logs
|> Enum.at(1)
|> assert_simple_activity_log(
action: "insert",
originator_type: "account",
target_type: "wallet"
)

logs
|> Enum.at(2)
|> assert_activity_log(
action: "insert",
originator: user,
target: account,
changes: %{
"metadata" => %{"something" => "interesting"},
"name" => "A test account",
"parent_uuid" => parent.uuid
},
encrypted_changes: %{
"encrypted_metadata" => %{"something" => "secret"}
}
)
end

test "creates a new account with no parent_id" do
Expand Down Expand Up @@ -343,6 +361,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do
test "generates an activity log" do
user = get_test_admin()
account = User.get_account(user)
timestamp = DateTime.utc_now()

request_data =
params_for(:account, %{
Expand All @@ -357,24 +376,24 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do
assert response["success"] == true

account = Account.get(response["data"]["id"])
log = get_last_activity_log(account)

assert log.action == "update"
assert log.inserted_at != nil
assert log.originator_type == "user"
assert log.originator_uuid == user.uuid
assert log.target_type == "account"
assert log.target_uuid == account.uuid

assert log.target_changes == %{
"name" => "updated_name",
"parent_uuid" => account.parent_uuid,
"description" => "updated_description"
}

assert log.target_encrypted_changes == %{
"encrypted_metadata" => %{"something" => "secret"}
}
logs = get_all_activity_logs_since(timestamp)
assert Enum.count(logs) == 1

logs
|> Enum.at(0)
|> assert_activity_log(
action: "update",
originator: user,
target: account,
changes: %{
"name" => "updated_name",
"parent_uuid" => account.parent_uuid,
"description" => "updated_description"
},
encrypted_changes: %{
"encrypted_metadata" => %{"something" => "secret"}
}
)
end

test "updates the account's categories" do
Expand Down Expand Up @@ -571,5 +590,41 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do
assert response["data"]["description"] ==
"You are not allowed to perform the requested operation."
end

test "generates an activity log" do
user = get_test_admin()
account = insert(:account)
timestamp = DateTime.utc_now()

response =
admin_user_request("/account.upload_avatar", %{
"id" => account.id,
"avatar" => %Plug.Upload{
path: "test/support/assets/test.jpg",
filename: "test.jpg"
}
})

assert response["success"] == true

account = Account.get(account.id)
logs = get_all_activity_logs_since(timestamp)
assert Enum.count(logs) == 1

logs
|> Enum.at(0)
|> assert_activity_log(
action: "update",
originator: user,
target: account,
changes: %{
"avatar" => %{
"file_name" => "test.jpg",
"updated_at" => Ecto.DateTime.to_iso8601(account.avatar.updated_at)
}
},
encrypted_changes: %{}
)
end
end
end
Expand Up @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
use AdminAPI.ConnCase, async: true
alias Ecto.UUID
alias EWallet.Web.Date
alias EWalletDB.{Account, User}
alias EWalletDB.{Account, User, Membership}

@redirect_url "http://localhost:4000/invite?email={email}&token={token}"

Expand Down Expand Up @@ -532,6 +532,40 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
assert response["data"]["description"] ==
"There is no role corresponding to the provided name."
end

test "generates an activity log" do
{:ok, user} = :user |> params_for() |> User.insert()
account = insert(:account)
role = insert(:role)
timestamp = DateTime.utc_now()

response =
admin_user_request("/account.assign_user", %{
user_id: user.id,
account_id: account.id,
role_name: role.name,
redirect_url: @redirect_url
})

assert response["success"] == true
membership = get_last_inserted(Membership)
logs = get_all_activity_logs_since(timestamp)
assert Enum.count(logs) == 1

logs
|> Enum.at(0)
|> assert_activity_log(
action: "insert",
originator: get_test_admin(),
target: membership,
changes: %{
"account_uuid" => account.uuid,
"role_uuid" => role.uuid,
"user_uuid" => user.uuid
},
encrypted_changes: %{}
)
end
end

describe "/account.unassign_user" do
Expand Down Expand Up @@ -599,5 +633,33 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
assert response["data"]["description"] ==
"You are not allowed to perform the requested operation."
end

test "generates an activity log" do
account = insert(:account)
{:ok, user} = :user |> params_for() |> User.insert()
membership = insert(:membership, %{account: account, user: user})

timestamp = DateTime.utc_now()

response =
admin_user_request("/account.unassign_user", %{
user_id: user.id,
account_id: account.id
})

assert response["success"] == true
logs = get_all_activity_logs_since(timestamp)
assert Enum.count(logs) == 1

logs
|> Enum.at(0)
|> assert_activity_log(
action: "delete",
originator: get_test_admin(),
target: membership,
changes: %{},
encrypted_changes: %{}
)
end
end
end