beam-community / jsonapi

JSON:API Serializer and Query Handler for Elixir
https://hex.pm/packages/jsonapi
MIT License
495 stars 78 forks source link

Incorrect InvalidQuery Errors Caused by `String.to_existing_atom/1` #317

Open kyleboe opened 5 months ago

kyleboe commented 5 months ago

This is kind of a wild edge case, but when a nested, valid include is defined, the QueryParser throws an error when that relationship is being included because the atom representation of the nested relationship has not yet been loaded in BEAM.

Example

defmodule PlaylistsController do
  plug JSONAPI.QueryParser,
    include: ~w(songs songs.artists),
    view: PlaylistView
  # . . .
end
defmodule PlaylistView do
  def relationships do
    [songs: SongView]
  end
end

defmodule SongView do
  def relationships do
    [artists: ArtistView]
  end
end

When calling to the controller with the include=songs.artists query param, a 500 error will be returned because of runtime loading of modules/atoms. Specifically this line in handle_nested_include/3: https://github.com/beam-community/jsonapi/blob/00d01cfd9c74307840a114da6ff606179bde4011/lib/jsonapi/plugs/query_parser.ex#L258

Proposed Solution

While I understand the reasoning for the current implementation (we don't want to cast external params to atoms and allow for DOS memory attacks), I think we can approach the problem differently.

Instead of converting the external params to match the internal setting. Could we convert the internal setting to match the incoming params. So instead of using a word list to define valid includes, could we use atoms to begin with and then cast them to strings and compare those strings with the ones from the internet? That way the atoms present in BEAM as soon as the module is loaded.

This may look something like this:

defmodule PlaylistsController do
  plug JSONAPI.QueryParser,
    include: [:songs, songs: :artists],
    view: PlaylistView
  # . . .
end
mattpolzin commented 4 months ago

So instead of using a word list to define valid includes, could we use atoms to begin with and then cast them to strings and compare those strings with the ones from the internet?

Something along those lines is probably preferable to the existing implementation in more than one way. We need to decide if it is good or bad (or unimportant) that the existing implementation takes nested include options literally (songs.artists means "including artists on songs is allowed" not "including songs is allowed and including artists on songs is allowed"). The current behavior was a side effect of the quickest implementation possible, rather than a carefully considered decision.

I'd like to start planning for a v2 release of the library so we can introduce some breaking changes and this could be one of them.

github-actions[bot] commented 3 months ago

This issue has been automatically marked as "stale:discard". We are sorry that we haven't been able to prioritize it yet. If this issue still relevant, please leave any comment if you have any new additional information that helps to solve this issue. We encourage you to create a pull request, if you can. We are happy to help you with that.