Fix authorization bypass in seeds and validations
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Add authorize?: false to all bootstrap operations in seeds.exs - Fix user-linking validation to respect authorize? context flag - Prevents authorization errors during initial setup when no actor exists yet
This commit is contained in:
parent
67b5d623cf
commit
079d270768
2 changed files with 47 additions and 17 deletions
|
|
@ -407,8 +407,16 @@ defmodule Mv.Membership.Member do
|
||||||
actor = Map.get(changeset.context || %{}, :actor)
|
actor = Map.get(changeset.context || %{}, :actor)
|
||||||
|
|
||||||
# Check the current state of the user in the database
|
# Check the current state of the user in the database
|
||||||
# Pass actor to ensure proper authorization (User might have policies in future)
|
# Check if authorization is disabled in the parent operation's context
|
||||||
case Ash.get(Mv.Accounts.User, user_id, actor: actor) do
|
# Access private context where authorize? flag is stored
|
||||||
|
authorize? =
|
||||||
|
case get_in(changeset.context, [:private, :authorize?]) do
|
||||||
|
false -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pass actor and authorize? to ensure proper authorization (User might have policies in future)
|
||||||
|
case Ash.get(Mv.Accounts.User, user_id, actor: actor, authorize?: authorize?) do
|
||||||
# User is free to be linked
|
# User is free to be linked
|
||||||
{:ok, %{member_id: nil}} ->
|
{:ok, %{member_id: nil}} ->
|
||||||
:ok
|
:ok
|
||||||
|
|
|
||||||
|
|
@ -161,24 +161,30 @@ end
|
||||||
# This handles both existing users (e.g., from OIDC) and newly created users
|
# This handles both existing users (e.g., from OIDC) and newly created users
|
||||||
case Accounts.User
|
case Accounts.User
|
||||||
|> Ash.Query.filter(email == ^admin_email)
|
|> Ash.Query.filter(email == ^admin_email)
|
||||||
|> Ash.read_one(domain: Mv.Accounts) do
|
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
|
||||||
{:ok, existing_admin_user} when not is_nil(existing_admin_user) ->
|
{:ok, existing_admin_user} when not is_nil(existing_admin_user) ->
|
||||||
# User already exists (e.g., via OIDC) - assign admin role
|
# User already exists (e.g., via OIDC) - assign admin role
|
||||||
|
# Use authorize?: false for bootstrap - this is initial setup
|
||||||
existing_admin_user
|
existing_admin_user
|
||||||
|> Ash.Changeset.for_update(:update, %{})
|
|> Ash.Changeset.for_update(:update, %{})
|
||||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||||
|> Ash.update!()
|
|> Ash.update!(authorize?: false)
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
# User doesn't exist - create admin user with password
|
# User doesn't exist - create admin user with password
|
||||||
Accounts.create_user!(%{email: admin_email}, upsert?: true, upsert_identity: :unique_email)
|
# Use authorize?: false for bootstrap - no admin user exists yet to use as actor
|
||||||
|
Accounts.create_user!(%{email: admin_email},
|
||||||
|
upsert?: true,
|
||||||
|
upsert_identity: :unique_email,
|
||||||
|
authorize?: false
|
||||||
|
)
|
||||||
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|
||||||
|> Ash.update!()
|
|> Ash.update!(authorize?: false)
|
||||||
|> then(fn user ->
|
|> then(fn user ->
|
||||||
user
|
user
|
||||||
|> Ash.Changeset.for_update(:update, %{})
|
|> Ash.Changeset.for_update(:update, %{})
|
||||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||||
|> Ash.update!()
|
|> Ash.update!(authorize?: false)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
|
|
@ -190,10 +196,10 @@ end
|
||||||
admin_user_with_role =
|
admin_user_with_role =
|
||||||
case Accounts.User
|
case Accounts.User
|
||||||
|> Ash.Query.filter(email == ^admin_email)
|
|> Ash.Query.filter(email == ^admin_email)
|
||||||
|> Ash.read_one(domain: Mv.Accounts) do
|
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
|
||||||
{:ok, user} when not is_nil(user) ->
|
{:ok, user} when not is_nil(user) ->
|
||||||
user
|
user
|
||||||
|> Ash.load!(:role)
|
|> Ash.load!(:role, authorize?: false)
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
raise "Admin user not found after creation/assignment"
|
raise "Admin user not found after creation/assignment"
|
||||||
|
|
@ -209,13 +215,14 @@ system_user_email = Mv.Helpers.SystemActor.system_user_email()
|
||||||
|
|
||||||
case Accounts.User
|
case Accounts.User
|
||||||
|> Ash.Query.filter(email == ^system_user_email)
|
|> Ash.Query.filter(email == ^system_user_email)
|
||||||
|> Ash.read_one(domain: Mv.Accounts) do
|
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
|
||||||
{:ok, existing_system_user} when not is_nil(existing_system_user) ->
|
{:ok, existing_system_user} when not is_nil(existing_system_user) ->
|
||||||
# System user already exists - ensure it has admin role
|
# System user already exists - ensure it has admin role
|
||||||
|
# Use authorize?: false for bootstrap
|
||||||
existing_system_user
|
existing_system_user
|
||||||
|> Ash.Changeset.for_update(:update, %{})
|
|> Ash.Changeset.for_update(:update, %{})
|
||||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||||
|> Ash.update!()
|
|> Ash.update!(authorize?: false)
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
# System user doesn't exist - create it with admin role
|
# System user doesn't exist - create it with admin role
|
||||||
|
|
@ -224,13 +231,15 @@ case Accounts.User
|
||||||
# - No OIDC ID (oidc_id = nil) - prevents OIDC login
|
# - No OIDC ID (oidc_id = nil) - prevents OIDC login
|
||||||
# - This user is ONLY for internal system operations via SystemActor
|
# - This user is ONLY for internal system operations via SystemActor
|
||||||
# If either hashed_password or oidc_id is set, the user could potentially log in
|
# If either hashed_password or oidc_id is set, the user could potentially log in
|
||||||
|
# Use authorize?: false for bootstrap - system user creation happens before system actor exists
|
||||||
Accounts.create_user!(%{email: system_user_email},
|
Accounts.create_user!(%{email: system_user_email},
|
||||||
upsert?: true,
|
upsert?: true,
|
||||||
upsert_identity: :unique_email
|
upsert_identity: :unique_email,
|
||||||
|
authorize?: false
|
||||||
)
|
)
|
||||||
|> Ash.Changeset.for_update(:update, %{})
|
|> Ash.Changeset.for_update(:update, %{})
|
||||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||||
|> Ash.update!()
|
|> Ash.update!(authorize?: false)
|
||||||
|
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
# Log error but don't fail seeds - SystemActor will fall back to admin user
|
# Log error but don't fail seeds - SystemActor will fall back to admin user
|
||||||
|
|
@ -397,9 +406,20 @@ additional_users = [
|
||||||
|
|
||||||
created_users =
|
created_users =
|
||||||
Enum.map(additional_users, fn user_attrs ->
|
Enum.map(additional_users, fn user_attrs ->
|
||||||
Accounts.create_user!(user_attrs, upsert?: true, upsert_identity: :unique_email)
|
# Use admin user as actor for additional user creation (not bootstrap)
|
||||||
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|
user =
|
||||||
|> Ash.update!()
|
Accounts.create_user!(user_attrs,
|
||||||
|
upsert?: true,
|
||||||
|
upsert_identity: :unique_email,
|
||||||
|
actor: admin_user_with_role
|
||||||
|
)
|
||||||
|
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|
||||||
|
|> Ash.update!(actor: admin_user_with_role)
|
||||||
|
|
||||||
|
# Reload user to ensure all fields (including member_id) are loaded
|
||||||
|
Accounts.User
|
||||||
|
|> Ash.Query.filter(id == ^user.id)
|
||||||
|
|> Ash.read_one!(domain: Mv.Accounts, actor: admin_user_with_role)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
# Create members with linked users to demonstrate the 1:1 relationship
|
# Create members with linked users to demonstrate the 1:1 relationship
|
||||||
|
|
@ -449,11 +469,13 @@ Enum.with_index(linked_members)
|
||||||
member =
|
member =
|
||||||
if user.member_id == nil do
|
if user.member_id == nil do
|
||||||
# User is free, create member and link - use upsert to prevent duplicates
|
# User is free, create member and link - use upsert to prevent duplicates
|
||||||
|
# Use authorize?: false for User lookup during relationship management (bootstrap phase)
|
||||||
Membership.create_member!(
|
Membership.create_member!(
|
||||||
Map.put(member_attrs_without_fee_type, :user, %{id: user.id}),
|
Map.put(member_attrs_without_fee_type, :user, %{id: user.id}),
|
||||||
upsert?: true,
|
upsert?: true,
|
||||||
upsert_identity: :unique_email,
|
upsert_identity: :unique_email,
|
||||||
actor: admin_user_with_role
|
actor: admin_user_with_role,
|
||||||
|
authorize?: false
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
# User already has a member, just create the member without linking - use upsert to prevent duplicates
|
# User already has a member, just create the member without linking - use upsert to prevent duplicates
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue