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

Commit

Permalink
User tracking/auditing (#457)
Browse files Browse the repository at this point in the history
* Setup Audits for users insertion

* Add VirtualStruct type and System struct

* Add audit system for users

* Move Bamboo libs to the ewallet sub app

* Handle originator = :self

* Fix AdminAPI test with Audit introduction

* Fix eWallet tests with Audit feature

* Switch ForgetPasswordRequest from being deleted to being disabled

* Fix tests and style

* Fix Originator aliases

* Add tests and improve readability

* Fix randomly failing test

* Fix Audit.all_for_target

* Add type specs

* Add a test for all_for_target

* Fix typespecs format
  • Loading branch information
Thibault authored Sep 27, 2018
1 parent 26183b6 commit bfef082
Show file tree
Hide file tree
Showing 55 changed files with 903 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AccountMembershipController do
import AdminAPI.V1.ErrorHandler
alias AdminAPI.InviteEmail
alias EWallet.{AccountMembershipPolicy, EmailValidator}
alias EWallet.Web.{Inviter, UrlValidator}
alias EWallet.Web.{Inviter, Originator, UrlValidator}
alias EWalletDB.{Account, Membership, Role, User}

@doc """
Expand Down Expand Up @@ -34,7 +34,8 @@ defmodule AdminAPI.V1.AccountMembershipController do
{:ok, user_or_email} <- get_user_or_email(attrs),
%Role{} = role <- Role.get_by_name(attrs["role_name"]) || {:error, :role_name_not_found},
{:ok, redirect_url} <- validate_redirect_url(attrs["redirect_url"]),
{:ok, _} <- assign_or_invite(user_or_email, account, role, redirect_url) do
originator <- Originator.extract(conn.assigns),
{:ok, _} <- assign_or_invite(user_or_email, account, role, redirect_url, originator) do
render(conn, :empty, %{success: true})
else
{true, :user_id_not_found} ->
Expand Down Expand Up @@ -88,17 +89,24 @@ defmodule AdminAPI.V1.AccountMembershipController do
end
end

defp assign_or_invite(email, account, role, redirect_url) when is_binary(email) do
defp assign_or_invite(email, account, role, redirect_url, originator) when is_binary(email) do
case EmailValidator.validate(email) do
{:ok, email} ->
Inviter.invite_admin(email, account, role, redirect_url, &InviteEmail.create/2)
Inviter.invite_admin(
email,
account,
role,
redirect_url,
originator,
&InviteEmail.create/2
)

error ->
error
end
end

defp assign_or_invite(user, account, role, redirect_url) do
defp assign_or_invite(user, account, role, redirect_url, _originator) do
case User.get_status(user) do
:pending_confirmation ->
user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule AdminAPI.V1.ResetPasswordController do
when not is_nil(email) and not is_nil(redirect_url) do
with {:ok, redirect_url} <- validate_redirect_url(redirect_url),
%User{} = user <- User.get_by_email(email) || :user_email_not_found,
{_, _} <- ForgetPasswordRequest.delete_all(user),
{_, _} <- ForgetPasswordRequest.disable_all_for(user),
%ForgetPasswordRequest{} = request <- ForgetPasswordRequest.generate(user),
%Email{} = email_object <- ForgetPasswordEmail.create(request, redirect_url),
%Email{} <- Mailer.deliver_now(email_object) do
Expand Down Expand Up @@ -48,8 +48,9 @@ defmodule AdminAPI.V1.ResetPasswordController do
) do
with %User{} = user <- get_user(email),
%ForgetPasswordRequest{} = request <- get_request(user, token),
attrs <- Map.put(attrs, "originator", request),
{:ok, %User{} = user} <- update_password(request, attrs) do
_ = ForgetPasswordRequest.delete_all(user)
_ = ForgetPasswordRequest.disable_all_for(user)
render(conn, :empty, %{success: true})
else
error when is_atom(error) ->
Expand All @@ -76,7 +77,8 @@ defmodule AdminAPI.V1.ResetPasswordController do
}) do
User.update(request.user, %{
password: password,
password_confirmation: password_confirmation
password_confirmation: password_confirmation,
originator: request
})
end
end
12 changes: 10 additions & 2 deletions apps/admin_api/lib/admin_api/v1/controllers/self_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.SelfController do
import AdminAPI.V1.ErrorHandler
alias AdminAPI.V1.{AccountHelper, AccountView, UserView}
alias Ecto.Changeset
alias EWallet.Web.{Paginator, Preloader, SearchParser, SortParser}
alias EWallet.Web.{Originator, Paginator, Preloader, SearchParser, SortParser}
alias EWalletDB.{Account, User}

@mapped_fields %{
Expand All @@ -30,6 +30,8 @@ defmodule AdminAPI.V1.SelfController do
"""
def update(conn, attrs) do
with {:ok, current_user} <- permit(:update, conn.assigns),
originator <- Originator.extract(conn.assigns),
attrs <- Map.put(attrs, "originator", originator),
{:ok, user} <- User.update_without_password(current_user, attrs) do
respond_single(user, conn)
else
Expand All @@ -42,7 +44,9 @@ defmodule AdminAPI.V1.SelfController do
Uploads an image as avatar for the current user.
"""
def upload_avatar(conn, %{"avatar" => _} = attrs) do
with {:ok, current_user} <- permit(:update, conn.assigns) do
with {:ok, current_user} <- permit(:update, conn.assigns),
originator <- Originator.extract(conn.assigns),
attrs <- Map.put(attrs, "originator", originator) do
current_user
|> User.store_avatar(attrs)
|> respond_single(conn)
Expand Down Expand Up @@ -89,6 +93,10 @@ defmodule AdminAPI.V1.SelfController do
end

# Respond with a single admin
defp respond_single({:ok, user}, conn) do
render(conn, UserView, :user, %{user: user})
end

defp respond_single(%User{} = user, conn) do
render(conn, UserView, :user, %{user: user})
end
Expand Down
12 changes: 9 additions & 3 deletions apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule AdminAPI.V1.UserController do
alias AdminAPI.V1.AccountHelper
alias Ecto.Changeset
alias EWallet.UserPolicy
alias EWallet.Web.{Paginator, SearchParser, SortParser}
alias EWallet.Web.{Originator, Paginator, SearchParser, SortParser}
alias EWalletDB.{Account, AccountUser, User, UserQuery}

# The field names to be mapped into DB column names.
Expand Down Expand Up @@ -112,6 +112,8 @@ defmodule AdminAPI.V1.UserController do
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
def create(conn, attrs) do
with :ok <- permit(:create, conn.assigns, nil),
originator <- Originator.extract(conn.assigns),
attrs <- Map.put(attrs, "originator", originator),
{:ok, user} <- User.insert(attrs),
%Account{} = account <- AccountHelper.get_current_account(conn),
{:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid) do
Expand All @@ -136,7 +138,9 @@ defmodule AdminAPI.V1.UserController do
)
when is_binary(id) and byte_size(id) > 0 do
with %User{} = user <- User.get(id) || {:error, :unauthorized},
:ok <- permit(:update, conn.assigns, user) do
:ok <- permit(:update, conn.assigns, user),
originator <- Originator.extract(conn.assigns),
attrs <- Map.put(attrs, "originator", originator) do
user
|> User.update(attrs)
|> respond_single(conn)
Expand All @@ -154,7 +158,9 @@ defmodule AdminAPI.V1.UserController do
)
when is_binary(id) and byte_size(id) > 0 do
with %User{} = user <- User.get_by_provider_user_id(id) || {:error, :unauthorized},
:ok <- permit(:update, conn.assigns, user) do
:ok <- permit(:update, conn.assigns, user),
originator <- Originator.extract(conn.assigns),
attrs <- Map.put(attrs, "originator", originator) do
user
|> User.update(attrs)
|> respond_single(conn)
Expand Down
2 changes: 0 additions & 2 deletions apps/admin_api/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ defmodule AdminAPI.Mixfile do
{:cowboy, "~> 1.0"},
{:cors_plug, "~> 1.5"},
{:sentry, "~> 6.2.0"},
{:bamboo, "~> 0.8"},
{:bamboo_smtp, "~> 1.4.0"},
{:bodyguard, "~> 2.2"},
{:deferred_config, "~> 0.1.0"},
{:ewallet_db, in_umbrella: true},
Expand Down
4 changes: 2 additions & 2 deletions apps/admin_api/test/admin_api/emails/invite_email_test.exs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
defmodule AdminAPI.InviteEmailTest do
use AdminAPI.ConnCase
alias AdminAPI.InviteEmail
alias EWalletDB.Invite
alias EWalletDB.{Invite, User}

defp create_email(email) do
admin = insert(:admin, email: email)
{:ok, admin} = :admin |> params_for(email: email) |> User.insert()
{:ok, invite} = Invite.generate(admin)

{InviteEmail.create(invite, "https://invite_url/?email={email}&token={token}"), invite.token}
Expand Down
5 changes: 3 additions & 2 deletions apps/admin_api/test/admin_api/v1/auth/admin_api_auth_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule AdminAPI.Web.V1.AdminAPIAuthTest do
use AdminAPI.ConnCase, async: true
alias AdminAPI.V1.AdminAPIAuth
alias EWalletDB.User

def authenticate(scheme, user_id, token) do
encoded_key = Base.encode64(user_id <> ":" <> token)
Expand All @@ -11,7 +12,7 @@ defmodule AdminAPI.Web.V1.AdminAPIAuthTest do
end

setup do
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()

%{
user: user,
Expand All @@ -27,7 +28,7 @@ defmodule AdminAPI.Web.V1.AdminAPIAuthTest do
assert auth.authenticated == true
assert auth.auth_scheme == :admin
assert auth.auth_scheme_name == "OMGAdmin"
assert auth.admin_user == meta.user
assert auth.admin_user.uuid == meta.user.uuid
assert auth.auth_user_id == meta.user.id
assert auth.auth_auth_token == meta.auth_token.token
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
defmodule AdminAPI.Web.V1.AdminUserAuthTest do
use AdminAPI.ConnCase, async: true
alias AdminAPI.V1.AdminUserAuth
alias EWalletDB.AuthToken
alias EWalletDB.{AuthToken, User}

def auth_header(user_id, token) do
encoded_key = Base.encode64(user_id <> ":" <> token)
AdminUserAuth.authenticate(%{auth_header: "OMGAdmin #{encoded_key}"})
end

setup do
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()

%{
user: user,
Expand All @@ -22,7 +22,7 @@ defmodule AdminAPI.Web.V1.AdminUserAuthTest do
auth = auth_header(meta.user.id, meta.auth_token.token)

assert auth.authenticated == true
assert auth.admin_user == meta.user
assert auth.admin_user.uuid == meta.user.uuid
assert auth.auth_user_id == meta.user.id
assert auth.auth_auth_token == meta.auth_token.token
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
defmodule AdminAPI.V1.UserChannelTest do
use AdminAPI.ChannelCase, async: false
alias AdminAPI.V1.UserChannel
alias EWalletDB.User

describe "join/3 as provider" do
test "joins the channel with authenticated account and valid user ID" do
account = insert(:account)
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()

{res, _, socket} =
"test"
Expand All @@ -19,7 +20,7 @@ defmodule AdminAPI.V1.UserChannelTest do

test "joins the channel with authenticated account and valid provider user ID" do
account = insert(:account)
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()

{res, _, socket} =
"test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
master = Account.get_master_account()
admin = get_test_admin()
account = insert(:account)
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()
role = insert(:role)
_ = insert(:membership, %{account: account, user: user, role: role})

Expand Down Expand Up @@ -187,9 +187,11 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do

describe "/account.assign_user" do
test "returns empty success if assigned with user_id successfully" do
{:ok, user} = :user |> params_for() |> User.insert()

response =
admin_user_request("/account.assign_user", %{
user_id: insert(:user).id,
user_id: user.id,
account_id: insert(:account).id,
role_name: insert(:role).name,
redirect_url: @redirect_url
Expand Down Expand Up @@ -309,9 +311,11 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
end

test "returns an error if the given account id does not exist" do
{:ok, user} = :user |> params_for() |> User.insert()

response =
admin_user_request("/account.assign_user", %{
user_id: insert(:user).id,
user_id: user.id,
account_id: "acc_12345678901234567890123456",
role_name: insert(:role).name,
redirect_url: @redirect_url
Expand All @@ -326,9 +330,11 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
end

test "returns an error if the given role does not exist" do
{:ok, user} = :user |> params_for() |> User.insert()

response =
admin_user_request("/account.assign_user", %{
user_id: insert(:user).id,
user_id: user.id,
account_id: insert(:account).id,
role_name: "invalid_role",
redirect_url: @redirect_url
Expand All @@ -346,7 +352,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
describe "/account.unassign_user" do
test "returns empty success if unassigned successfully" do
account = insert(:account)
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()
_membership = insert(:membership, %{account: account, user: user})

response =
Expand All @@ -360,7 +366,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
end

test "returns an error if the user was not previously assigned to the account" do
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()
account = insert(:account)

response =
Expand Down Expand Up @@ -393,9 +399,11 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do
end

test "returns an error if the given account id does not exist" do
{:ok, user} = :user |> params_for() |> User.insert()

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do
use AdminAPI.ConnCase, async: true
alias EWallet.Web.V1.{AccountSerializer, UserSerializer}
alias EWalletDB.{Account, AuthToken, Membership, Repo, Role, User}
alias EWalletDB.{Account, AuthToken, Membership, Repo, Role, System, User}

describe "/admin.login" do
test "responds with a new auth token if the given email and password are valid" do
Expand Down Expand Up @@ -92,7 +92,10 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do
{:ok, _user} =
[email: @user_email]
|> User.get_by()
|> User.update_without_password(%{invite_uuid: insert(:invite).uuid})
|> User.update_without_password(%{
invite_uuid: insert(:invite).uuid,
originator: %System{}
})

response =
unauthenticated_request("/admin.login", %{email: @user_email, password: @password})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do
use AdminAPI.ConnCase, async: true
alias Ecto.UUID
alias EWalletDB.User

describe "/admin.all" do
test "returns a list of admins and pagination data" do
Expand Down Expand Up @@ -64,7 +65,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do
end

test "returns 'user:id_not_found' if the given ID is not an admin" do
user = insert(:user)
{:ok, user} = :user |> params_for() |> User.insert()
response = admin_user_request("/admin.get", %{"id" => user.id})

refute response["success"]
Expand Down
Loading

0 comments on commit bfef082

Please sign in to comment.