Skip to content

Commit

Permalink
User identities context assigns global abilities to a oidc user
Browse files Browse the repository at this point in the history
  • Loading branch information
CDimonaco committed Aug 7, 2024
1 parent f59d636 commit b750c38
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 2 deletions.
74 changes: 72 additions & 2 deletions lib/trento/user_identities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,89 @@ defmodule Trento.UserIdentities do
@moduledoc """
The UserIdentities context, serves as custom context for PowAssent
"""
require Logger

use PowAssent.Ecto.UserIdentities.Context,
repo: Trento.Repo,
user: Trento.Users.User

import Ecto.Query, warn: false

alias Trento.Abilities.UsersAbilities
alias Trento.Repo
alias Trento.Users
alias Trento.Users.User

@impl true
@doc """
redefining the PowAssent upsert method, if a IDP user is associated with a locked user
redefining the PowAssent create user method, this is called when the user login through idp but
does not exist in our user database
"""
def create_user(user_identity_params, user_params, user_id_params) do
case pow_assent_create_user(user_identity_params, user_params, user_id_params) do
{:ok, %User{} = user} ->
{:ok, maybe_assign_global_abilities(user)}

error ->
error
end
end

@impl true
@doc """
redefining the PowAssent upsert method, if a IDP user is associated with a locked user,
this is called when the user login with IDP and exist in our database with or without a user identity
"""
def upsert(%User{locked_at: locked_at} = user, _)
when not is_nil(locked_at),
do: {:error, {:user_not_allowed, user}}

def upsert(user, user_identity_params), do: pow_assent_upsert(user, user_identity_params)
def upsert(user, user_identity_params) do
pow_assent_upsert(maybe_assign_global_abilities(user), user_identity_params)
end

defp maybe_assign_global_abilities(user) do
if admin_user?(user) do
{:ok, user} = assign_global_abilities(user)

user
else
user
end
end

# assign_global_abilities assigns the global ability to the admin user retrieved from oidc
# we don't use the Users context directly because it's forbidden to update an admin user.
# The only exception is in this particular flow, because it's strictly needed
defp assign_global_abilities(%User{} = user) do
result =
Ecto.Multi.new()
|> Ecto.Multi.put(:user, user)
|> Ecto.Multi.delete_all(
:delete_abilities,
fn %{user: %User{id: user_id}} ->
from(u in UsersAbilities, where: u.user_id == ^user_id)
end
)
|> Ecto.Multi.insert(:add_global_ability, fn %{user: %User{id: user_id}} ->
UsersAbilities.changeset(%UsersAbilities{}, %{user_id: user_id, ability_id: 1})
end)
|> Repo.transaction()

case result do
{:ok, %{user: %{id: user_id}}} ->
# reload the current user with full assoc
Users.get_user(user_id)

{:error, _, changeset_error, _} ->
Logger.error(
"could not assign the global abilities to the oidc admin user #{inspect(changeset_error)}"
)

{:error, :assign_global_abilities}
end
end

defp admin_user?(%User{username: username}),
do: username == Application.fetch_env!(:trento, :admin_user)
end
68 changes: 68 additions & 0 deletions test/trento/user_identities_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule Trento.UserIdentitiesTest do
use Trento.DataCase

alias Trento.UserIdentities
alias Trento.Users
alias Trento.Users.User

import Trento.Factory

describe "create_user/3" do
test "should create the user and assign the global abilities when the user is the default admin" do
%{username: username} = user = build(:user)
Application.put_env(:trento, :admin_user, username)

user_id_params = nil

user_identity_params = %{
"provider" => "test_provider",
"token" => %{"access_token" => "access_token"},
"uid" => Faker.UUID.v4(),
"userinfo" => %{
"email" => user.email,
"sid" => nil,
"sub" => user.username,
"username" => user.username
}
}

user_params = %{
"email" => user.email,
"sid" => nil,
"username" => user.username
}

assert {:ok, %User{id: user_id}} =
UserIdentities.create_user(user_identity_params, user_params, user_id_params)

{:ok, %{abilities: abilities}} = Users.get_user(user_id)
assert [%{id: 1}] = abilities

Application.put_env(:trento, :admin_user, "admin")
end
end

describe "upsert/2" do
test "should assign the global abilities to a user when the user is the default admin" do
%{id: user_id, username: username} = user = insert(:user)
Application.put_env(:trento, :admin_user, username)

{:ok, _} =
UserIdentities.upsert(user, %{"uid" => user_id, "provider" => "test_provider"})

{:ok, %{abilities: abilities}} = Users.get_user(user_id)
assert [%{id: 1}] = abilities
Application.put_env(:trento, :admin_user, "admin")
end

test "should not assign the global abilities to a user when the user is not the default admin" do
%{id: user_id} = user = insert(:user)

{:ok, _} =
UserIdentities.upsert(user, %{"uid" => user_id, "provider" => "test_provider"})

{:ok, %{abilities: abilities}} = Users.get_user(user_id)
assert [] = abilities
end
end
end
77 changes: 77 additions & 0 deletions test/trento_web/controllers/session_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule TrentoWeb.SessionControllerTest do
alias TrentoWeb.Auth.RefreshToken
alias TrentoWeb.OpenApi.V1.ApiSpec

alias Trento.Users
alias Trento.Users.User

setup [:set_mox_from_context, :verify_on_exit!]
Expand Down Expand Up @@ -463,6 +464,7 @@ defmodule TrentoWeb.SessionControllerTest do
)

Application.put_env(:trento, :pow_assent,
user_identities_context: Trento.UserIdentities,
providers: [
test_provider: [strategy: TestProvider, test_user: user]
]
Expand Down Expand Up @@ -491,6 +493,7 @@ defmodule TrentoWeb.SessionControllerTest do
)

Application.put_env(:trento, :pow_assent,
user_identities_context: Trento.UserIdentities,
providers: [
test_provider: [strategy: TestProvider, test_user: user]
]
Expand All @@ -508,6 +511,80 @@ defmodule TrentoWeb.SessionControllerTest do
|> assert_schema("Credentials", api_spec)
end

test "should return the credentials when the oidc callback flow is completed without errors and assign the global abilities when the oidc user is the default admin and does not exists on trento",
%{conn: conn, api_spec: api_spec} do
%{username: username} = user = build(:user)
Application.put_env(:trento, :admin_user, username)

expect(
Joken.CurrentTime.Mock,
:current_time,
6,
fn ->
1_671_715_992
end
)

Application.put_env(:trento, :pow_assent,
user_identities_context: Trento.UserIdentities,
providers: [
test_provider: [strategy: TestProvider, test_user: user]
]
)

valid_params = %{"code" => "valid", "session_params" => %{"a" => 1}}

conn = post(conn, ~p"/api/session/test_provider/callback?#{valid_params}")

conn
|> json_response(200)
|> assert_schema("Credentials", api_spec)

%User{id: user_id} = Users.get_by(username: username)
{:ok, %User{abilities: abilities}} = Users.get_user(user_id)
assert [%{id: 1}] = abilities

Application.put_env(:trento, :admin_user, "admin")
end

test "should return the credentials when the oidc callback flow is completed without errors and assign the global abilities when the oidc user is the default admin and already exists on trento",
%{conn: conn, api_spec: api_spec} do
%{username: username, id: user_id} = user = insert(:user)
Application.put_env(:trento, :admin_user, username)

expect(
Joken.CurrentTime.Mock,
:current_time,
6,
fn ->
1_671_715_992
end
)

Application.put_env(:trento, :pow_assent,
user_identities_context: Trento.UserIdentities,
providers: [
test_provider: [strategy: TestProvider, test_user: user]
]
)

valid_params = %{"code" => "valid", "session_params" => %{"a" => 1}}

conn =
conn
|> Pow.Plug.assign_current_user(user, Pow.Plug.fetch_config(conn))
|> post(~p"/api/session/test_provider/callback?#{valid_params}")

conn
|> json_response(200)
|> assert_schema("Credentials", api_spec)

{:ok, %User{abilities: abilities}} = Users.get_user(user_id)
assert [%{id: 1}] = abilities

Application.put_env(:trento, :admin_user, "admin")
end

test "should return unauthorized when the oidc callback flow is completed without errors and the user is locked on trento",
%{conn: conn, api_spec: api_spec} do
user = insert(:user, locked_at: DateTime.utc_now())
Expand Down

0 comments on commit b750c38

Please sign in to comment.