From a4b06014f63fb3106baf2b1f3a44863211b87cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ot=C3=A1vio=20Biondo?= Date: Wed, 16 Jun 2021 14:58:47 -0300 Subject: [PATCH 1/2] Support functions in query scope authorization args --- lib/schema.ex | 11 +++++-- test/middlewares/schema_test.exs | 56 +++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/lib/schema.ex b/lib/schema.ex index a5f7ff3..6d6e559 100644 --- a/lib/schema.ex +++ b/lib/schema.ex @@ -128,15 +128,13 @@ defmodule Rajska.Schema do defp validate_args!(args) when is_map(args) do Enum.each(args, fn {field, value} when is_atom(field) and is_atom(value) -> :ok - {field, values} when is_atom(field) and is_list(values) -> validate_list_of_atoms!(values) + {field, values} when is_atom(field) and is_list(values) -> validate_list_of_atoms_or_function!(values) field_value -> raise "the following args option is invalid: #{inspect(field_value)}. Since the provided args is a map, you should provide an atom key and an atom or list of atoms value." end) end defp validate_args!(args) when is_list(args), do: validate_list_of_atoms!(args) - defp validate_args!(args) when is_atom(args), do: :ok - defp validate_args!(args), do: raise "the following args option is invalid: #{inspect(args)}" defp validate_list_of_atoms!(args) do @@ -145,4 +143,11 @@ defmodule Rajska.Schema do arg -> raise "the following args option is invalid: #{inspect(args)}. Expected a list of atoms, but found #{inspect(arg)}" end) end + + defp validate_list_of_atoms_or_function!(args) do + Enum.each(args, fn + arg when is_atom(arg) or is_function(arg) -> :ok + arg -> raise "the following args option is invalid: #{inspect(args)}. Expected a list of atoms or functions, but found #{inspect(arg)}" + end) + end end diff --git a/test/middlewares/schema_test.exs b/test/middlewares/schema_test.exs index 1854495..68d1265 100644 --- a/test/middlewares/schema_test.exs +++ b/test/middlewares/schema_test.exs @@ -145,14 +145,41 @@ defmodule Rajska.SchemaTest do ) end - test "Raises if args option is invalid" do - assert_raise( - RuntimeError, - ~r/Query get_user is configured incorrectly, the following args option is invalid: "args"/, - fn -> - defmodule SchemaInvalidArgs do + @args_option_test_cases [ + {false, :arg}, + {false, [:arg1, :arg2]}, + {false, %{arg: :arg}}, + {false, %{arg: [:arg]}}, + # This is not the correct usage of &Access.all/0 that is going to be used in the Kernel.get_in/2 in + # Rajska.QueryScopeAuthorization.get_scope_field_value/2, but this is necessary because it's not possible to use + # Access.all() (the correct usage). But for testing purpose only this is fine. + {false, %{arg: [&Access.all/0, :arg]}}, + {true, "args"}, + {true, 1}, + {true, ["args"]}, + {true, [1]}, + {true, %{arg: ["arg"]}}, + {true, %{arg: [1]}}, + {true, [&Access.all/0, :arg]}, + ] + + for {{should_raise, args_value}, index} <- Enum.with_index(@args_option_test_cases) do + @should_raise should_raise + @args_value args_value + @index index + @base_message "if args option is #{inspect(args_value)}" + @message if should_raise, do: "Raises #{@base_message}", else: "Not raises #{@base_message}" + + test @message do + args_value = @args_value + index = @index + + define_query_fn = fn -> + defmodule String.to_atom("SchemaInvalidArgs#{index}") do use Absinthe.Schema + @args_value args_value + def context(ctx), do: Map.put(ctx, :authorization, Authorization) def middleware(middleware, field, %{identifier: identifier}) @@ -164,13 +191,26 @@ defmodule Rajska.SchemaTest do query do field :get_user, :string do - middleware Rajska.QueryAuthorization, [permit: :user, scope: User, args: "args"] + middleware Rajska.QueryAuthorization, [permit: :user, scope: User, args: @args_value] resolve fn _args, _info -> {:ok, "bob"} end end end end end - ) + + if @should_raise do + invalid_value = if is_map(@args_value), do: @args_value[:arg], else: @args_value + escaped_invalid_value = invalid_value |> inspect() |> Regex.escape() + + assert_raise( + RuntimeError, + ~r/Query get_user is configured incorrectly, the following args option is invalid: #{escaped_invalid_value}/, + define_query_fn + ) + else + define_query_fn.() + end + end end test "Raises if optional option is not a boolean" do From ea874795421b6880df864281c8af02114ac90fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ot=C3=A1vio=20Biondo?= Date: Wed, 16 Jun 2021 16:13:08 -0300 Subject: [PATCH 2/2] Add docs for using access functions in query scope authorization args --- lib/middlewares/query_scope_authorization.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/middlewares/query_scope_authorization.ex b/lib/middlewares/query_scope_authorization.ex index ca9b8bf..5200d6c 100644 --- a/lib/middlewares/query_scope_authorization.ex +++ b/lib/middlewares/query_scope_authorization.ex @@ -61,6 +61,9 @@ defmodule Rajska.QueryScopeAuthorization do - `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/3`. It must define a struct. * `:args` - `%{user_id: [:params, :id]}`: where `user_id` is the scoped field and `id` is an argument nested inside the `params` argument. + This form also accepts a function in the array (the same way as described in the [Kernel.get_in/2](https://hexdocs.pm/elixir/Kernel.html#get_in/2-functions-as-keys)). + This way, we can also use `%{user_id: [:params, Access.all(), :id]}` and for an input arg like `params: [%{id: 1}, %{id: 2}]`, + the builded struct will have the value `%User{user_id: [1, 2]}`. - `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to `c:Rajska.Authorization.has_user_access?/3` - `[:code, :user_group_id]`: this is the same as `%{code: :code, user_group_id: :user_group_id}`, where `code` and `user_group_id` are both query arguments and scoped fields. * `:optional` (optional) - when set to true the arguments are optional, so if no argument is provided, the query will be authorized. Defaults to false.