Skip to content

Commit

Permalink
Add support for a JSON:API includes allowlist.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattpolzin committed Jun 12, 2023
1 parent 5e23b93 commit 1e51225
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 0 deletions.
23 changes: 23 additions & 0 deletions lib/jsonapi/plugs/query_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@ defmodule JSONAPI.QueryParser do
plug JSONAPI.QueryParser,
filter: ~w(title),
sort: ~w(created_at title),
include: ~w(others) # optionally specify a list of allowed includes.
view: MyView
```
If you specify which includes are allowed, any include name not in the list
will produce an error. If you omit the `include` list then all relationships
specified by the given resource will be allowed.
If your controller's index function receives a query with params inside those
bounds it will build a `JSONAPI.Config` that has all the validated and parsed
fields for your usage. The final configuration will be added to assigns
Expand Down Expand Up @@ -206,6 +211,8 @@ defmodule JSONAPI.QueryParser do
|> Enum.map(&underscore/1)

Enum.reduce(includes, [], fn inc, acc ->
check_include_validity!(inc, config)

if inc =~ ~r/\w+\.\w+/ do
acc ++ handle_nested_include(inc, valid_includes, config)
else
Expand All @@ -225,6 +232,22 @@ defmodule JSONAPI.QueryParser do
end)
end

defp check_include_validity!(key, %Config{opts: opts, view: view}) do
if opts do
check_include_validity!(key, Keyword.get(opts, :include), view)
end
end

defp check_include_validity!(key, allowed_includes, view) when is_list(allowed_includes) do
unless key in allowed_includes do
raise_invalid_include_query(key, view.type())
end
end

defp check_include_validity!(_key, nil, _view) do
# all includes are allowed if none are specified in input config
end

@spec handle_nested_include(key :: String.t(), valid_include :: list(), config :: Config.t()) ::
list() | no_return()
def handle_nested_include(key, valid_include, config) do
Expand Down
12 changes: 12 additions & 0 deletions test/jsonapi/plugs/query_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ defmodule JSONAPI.QueryParserTest do
end
end

test "parse_include/2 errors with limited allowed includes" do
config = struct(Config, view: MyView, opts: [include: ~w(author comments comments.user)])

assert_raise InvalidQuery, "invalid include, best_friends for type mytype", fn ->
parse_include(config, "best_friends,author")
end

assert parse_include(config, "author,comments").include == [:author, :comments]

assert parse_include(config, "author,comments.user").include == [:author, {:comments, :user}]
end

test "parse_fields/2 turns a fields map into a map of validated fields" do
config = struct(Config, view: MyView)
assert parse_fields(config, %{"mytype" => "id,text"}).fields == %{"mytype" => [:id, :text]}
Expand Down

0 comments on commit 1e51225

Please sign in to comment.