I’m not suggesting using |> cast_assoc(:user)
. What I am suggesting is putting the user_id
inside the magnet struct before sending it into the changeset function.
Thanks did not see your example, so I think I finally understand how this should work. I updated everything which makes sense but I’m getting the following error when I got the add new magnet view
function Magnify.Magnets.change_magnet/1 is undefined (module Magnify.Magnets is not available)
I have this is my controller
alias Magnify.Magnets
alias Magnify.Magnets.Magnet
alias Magnify.Repo
Never I think I got it working,
I’m getting
- (CompileError) lib/magnify/magnets/magnets.ex:54: User.struct/0 is undefined, cannot expand struct User
Do I need to alias the user?
Ok so I did this
def create_magnet(attrs, for: %Magnify.Coherence.User{id: user_id}) do
%Magnet{user_id: user_id}
|> Magnet.changeset(attrs)
|> Repo.insert()
end
which is fine now, when create the object or post I get
Postgrex expected a binary of 16 bytes, got 1. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.
I think most of elixir community vulnerable. Really thanks i see always cast_assocs or id in changesets. This is the first time i see your approach. But on the other hand we already modify user_id from controller so somebody cant put another user_id right ?
Thanks everyone, got it working, have to the community around Elixir is just amazing. Now to the next challenge / task
In your example above, yes. Since you use Map.put(params, "user_id", ...)
it replaces the "user_id"
provided in the params.
But what if you or someone else who starts working on your codebase forgets to use Map.put/3
or to delete the "user_id"
key from params
, or uses Map.put_new/3
in some other controller using the same changeset function (which casts user_id
), or replaces :user_id
with :owner_id
in the schema but forgets to update Map.put(params, "user_id", ...)
with Map.put(params, "owner_id", ...)
in the controller?
Then the "user_id"
from params
leaks into the changeset function and you have a vulnerability. Any user can send whatever "user_id"
they want thus completely bypassing any authentication logic you have.
The only way to avoid it, as I see it, is to not cast user_id
(or any other foreign key) inside the changeset functions but put it into the schema structs explicitly when needed.
@idi527 would you do the same thing for many_to_many
I have
schema "fluxes" do
field :description, :string
field :title, :string
many_to_many :magnets, Magnify.Magnets.Magnet, join_through: "magnets_fluxes"
timestamps()
end
@doc false
def changeset(flux, attrs) do
flux
|> cast(attrs, [:title, :description])
|> validate_required([:title, :description])
end
And not sure the correct to add magnets use puts_assoc but not sure?
I posted about this topic a few days ago:
The scenario might be similar but keep in mind that using cast_assoc requires you to handle the parent and child at the same time.
Hope this helps.
Best regards,
I’d probably use an ecto multi to insert the flux and create relationships between it and some magnets inside a single database transaction.
Adapted from Is there are way to bulk insert join records of a many-to-many relationship with Ecto?
# in the module containing your business logic
# here I suppose that the magnets already exist
# and we only need to associate them with the
# created flux
@spec create_flux(%{(String.t | atom) => term}, [magnet_id :: pos_integer]) :: {:ok, %Flux{}} | {:error, Ecto.Changeset.t}
def create_flux(flux_attrs, magnet_ids) do
alias Ecto.Multi
flux_changeset = Flux.changeset(%Flux{}, flux_attrs)
Multi.new()
|> Multi.insert(:flux, flux_changeset)
|> Multi.run(:magnet_relationships, fn %{flux: %Flux{id: flux_id}} ->
# prepare relationships to be inserted into the many-to-many table
relationships = Enum.map(magnet_ids, fn magnet_id ->
%{magnet_id: magnet_id, flux_id: flux_id}
end)
# https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert_all/3
{:ok, Repo.insert_all("magnets_fluxes", relationships)}
end)
|> Repo.transaction()
|> case do
{:ok, %{flux: flux}} -> {:ok, flux}
{:error, :flux, changeset, _changes} -> {:error, changeset}
end
end
Used like this
flux_attrs = %{title: "a flux", description: "a flux"}
magnet_ids = [1, 2, 3] # I suppose these already exist in the database
create_flux(flux_attrs, magnet_ids)
@idi527 this is great, thanks appreciate it
I’m getting - function Magnify.Magnets.create_flux/1 is undefined or private. Did you mean one of:
Called with 1 arguments
%{"description" => "fff", "magnet_id" => ["2", "3"], "title" => "aff"}
I have the following in my templates
<%= multiple_select f, :magnet_id, @magnets, class: "form-control" %>
and the controller
magnets = Magnets.list_magnets(params) |> Enum.map(&{&1.description, &1.id})
I’m thinking that is were the issue is ?
Ok I know what the issue, is it’s the calling the create which only has case Magnets.create_flux(flux_params) do
just need to figure how to get magent_ids as that is inside flux_params
"Parameters: %{"_csrf_token" => "VxwHMmkfLwMhPhxuHRVcfn0DGTgfEAAA1lDzBRjRLGIVyqsL67MTOA==", "_utf8" => "✓", "flux" => %{"description" => "aaa", "magnet_id" => ["2"], "title" => "aaaa"}}
"
Maybe
magnet_ids = params["magnet_id"] || []
create_flux(params, magnet_ids)
But then you might need to check that the user actually has access to the magnet ids in params
.
Well the create is like so
def create(conn, %{"flux" => flux_params}) do
IO.inspect flux_params
case Magnets.create_flux(flux_params) do
{:ok, flux} ->
conn
|> put_flash(:info, "Flux created successfully.")
|> redirect(to: flux_path(conn, :show, flux))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
and flux_params had the keys
"Parameters: %{"_csrf_token" => "VxwHMmkfLwMhPhxuHRVcfn0DGTgfEAAA1lDzBRjRLGIVyqsL67MTOA==", "_utf8" => "✓", "flux" => %{"description" => "aaa", "magnet_id" => ["2"], "title" => "aaaa"}}
def create(conn, %{"flux" => flux_params}) do
magnet_ids = flux_params["magnet_id"] || []
case Magnets.create_flux(flux_params, magnet_ids) do
{:ok, flux} ->
conn
|> put_flash(:info, "Flux created successfully.")
|> redirect(to: flux_path(conn, :show, flux))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
I did that got
Postgrex expected an integer in -9223372036854775808…9223372036854775807, got “1”. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.
Which is working but the many to many is the wrong format
what I have
many_to_many :magnets, Magnify.Magnets.Magnet, join_through: “magnets_fluxes”
def create(conn, %{"flux" => flux_params}) do
magnet_ids = Enum.map(flux_params["magnet_id"] || [], fn magnet_id ->
String.to_integer(magnet_id)
end)
case Magnets.create_flux(flux_params, magnet_ids) do
{:ok, flux} ->
conn
|> put_flash(:info, "Flux created successfully.")
|> redirect(to: flux_path(conn, :show, flux))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
Well It’s getting there I now got
schema Magnify.Magnets.Flux does not have association :magents
Which I have no idea what is going on I added the many to many ?
What’s the file / line where this error is raised? And what’s on that line?