Skip to content

Commit

Permalink
Merge 30fa409 into c12bd4a
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielpra1 committed Oct 18, 2019
2 parents c12bd4a + 30fa409 commit 2fb6003
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ All the following options are sent to [has_user_access?/4](https://hexdocs.pm/ra

* `:scope`
- `false`: disables scoping
- `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. It must implement a `Rajska.Authorization` behaviour and a `__schema__(:source)` function (used to check if the module is valid in `Rajska.Schema.validate_query_auth_config!/2`)
- `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. 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.
- `:id`: this is the same as `%{id: :id}`, where `:id` is both the query argument and the scoped field that will be passed to [has_user_access?/4](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/4)
Expand Down
22 changes: 12 additions & 10 deletions lib/middlewares/object_scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,25 @@ defmodule Rajska.ObjectScopeAuthorization do
end

# Object
defp result(%{fields: fields, emitter: %{schema_node: schema_node} = emitter} = result, context) do
defp result(%{fields: fields, emitter: %{schema_node: schema_node} = emitter, root_value: %scope{} = root_value} = result, context) do
type = Introspection.get_object_type(schema_node.type)
scope = Type.meta(type, :scope)
scope_by = Type.meta(type, :scope_by)

default_rule = Rajska.apply_auth_mod(context, :default_rule)
rule = Type.meta(type, :rule) || default_rule

case authorized?(scope, result.root_value, context, rule, type) do
case authorized?(scope, scope_by, root_value, context, rule, type) do
true -> %{result | fields: walk_result(fields, context)}
false -> Map.put(result, :errors, [error(emitter)])
end
end

# Invalid object
defp result(%{emitter: %{schema_node: schema_node}, root_value: root_value}, _context) do
type = Introspection.get_object_type(schema_node.type)
raise "Expected a Struct for object #{inspect(type.identifier)}, got #{inspect(root_value)}"
end

# List
defp result(%{values: values} = result, context) do
%{result | values: walk_result(values, context)}
Expand All @@ -141,20 +147,16 @@ defmodule Rajska.ObjectScopeAuthorization do
walk_result(fields, context, new_fields)
end

defp authorized?(nil, _values, _context, _, object), do: raise "No meta scope defined for object #{inspect object.identifier}"
defp authorized?(_scope, nil, _values, _context, _, object), do: raise "No meta scope_by defined for object #{inspect object.identifier}"

defp authorized?(false, _values, _context, _, _object), do: true
defp authorized?(_scope, false, _values, _context, _, _object), do: true

defp authorized?({scope, scope_field}, values, context, rule, _object) do
defp authorized?(scope, scope_field, values, context, rule, _object) do
field_value = Map.get(values, scope_field)

Rajska.apply_auth_mod(context, :has_context_access?, [context, scope, {scope_field, field_value}, rule])
end

defp authorized?(scope, values, context, rule, object) do
authorized?({scope, :id}, values, context, rule, object)
end

defp error(%{source_location: location, schema_node: %{type: type}}) do
%Phase.Error{
phase: __MODULE__,
Expand Down
4 changes: 1 addition & 3 deletions lib/middlewares/query_scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule Rajska.QueryScopeAuthorization do
* `:scope`
- `false`: disables scoping
- `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. It must implement a `Rajska.Authorization` behaviour and a `__schema__(:source)` function (used to check if the module is valid in `Rajska.Schema.validate_query_auth_config!/2`)
- `User`: a module that will be passed to `c:Rajska.Authorization.has_user_access?/4`. 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.
- `: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?/4`
Expand Down Expand Up @@ -96,8 +96,6 @@ defmodule Rajska.QueryScopeAuthorization do
raise "Error in query #{name}: no scope argument found in middleware Scope Authorization"
end

defp get_arguments_source!(%Resolution{source: source}, :source), do: source

defp get_arguments_source!(%Resolution{arguments: args}, _scope), do: args

def apply_scope_authorization(
Expand Down
8 changes: 3 additions & 5 deletions lib/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ defmodule Rajska.Schema do
@spec validate_query_auth_config!(
[
permit: atom(),
scope: false | :source | module(),
scope: false | module(),
args: %{} | [] | atom(),
optional: false | true,
rule: atom()
Expand Down Expand Up @@ -97,12 +97,10 @@ defmodule Rajska.Schema do

defp validate_scope!(false, _role, _authorization), do: :ok

defp validate_scope!(:source, _role, _authorization), do: :ok

defp validate_scope!(scope, _role, _authorization) when is_atom(scope) do
scope.__schema__(:source)
struct!(scope)
rescue
UndefinedFunctionError -> reraise ":scope option #{inspect(scope)} doesn't implement a __schema__(:source) function.", __STACKTRACE__
UndefinedFunctionError -> reraise ":scope option #{inspect(scope)} is not a struct.", __STACKTRACE__
end

defp validate_args!(args) when is_map(args) do
Expand Down
81 changes: 64 additions & 17 deletions test/middlewares/object_scope_authorization_test.exs
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
defmodule Rajska.ObjectScopeAuthorizationTest do
use ExUnit.Case, async: true

defmodule Wallet do
defstruct [
id: 1,
total: 10,
]
end

defmodule Company do
defstruct [
id: 1,
name: "User",
user_id: 1,
wallet: %Wallet{}
]
end

defmodule User do
defstruct [
id: 1,
name: "User",
email: "email@user.com"
email: "email@user.com",
company: nil,
companies: [],
not_scoped: nil,
]
end

def __schema__(:source), do: "users"
defmodule NotScoped do
defstruct [
id: 1,
]
end

defmodule Authorization do
Expand Down Expand Up @@ -41,14 +64,14 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
arg :user_id, non_null(:integer)

resolve fn args, _ ->
{:ok, %{
{:ok, %User{
id: args.user_id,
name: "bob",
company: %{
company: %Company{
id: 5,
user_id: args.user_id,
name: "company",
wallet: %{id: 1, total: 10}
wallet: %Wallet{id: 1, total: 10}
}
}}
end
Expand All @@ -58,7 +81,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
arg :user_id, non_null(:integer)

resolve fn args, _ ->
{:ok, %{
{:ok, %User{
id: args.user_id,
name: "bob"
}}
Expand All @@ -69,12 +92,12 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
arg :user_id, non_null(:integer)

resolve fn args, _ ->
{:ok, %{
{:ok, %User{
id: args.user_id,
name: "bob",
companies: [
%{id: 1, user_id: args.user_id, wallet: %{id: 2, total: 10}},
%{id: 2, user_id: args.user_id, wallet: %{id: 1, total: 10}},
%Company{id: 1, user_id: args.user_id, wallet: %Wallet{id: 2, total: 10}},
%Company{id: 2, user_id: args.user_id, wallet: %Wallet{id: 1, total: 10}},
]
}}
end
Expand All @@ -83,15 +106,22 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
field :object_not_scoped_query, :user do
arg :id, non_null(:integer)
resolve fn args, _ ->
{:ok, %{id: args.id, name: "bob", not_scoped: %{name: "name"}}}
{:ok, %User{id: args.id, name: "bob", not_scoped: %NotScoped{id: 1}}}
end
end

field :object_not_struct_query, :user do
arg :id, non_null(:integer)
resolve fn args, _ ->
{:ok, %{id: args.id, name: "bob"}}
end
end

field :users_query, list_of(:user) do
resolve fn _args, _ ->
{:ok, [
%{id: 1, name: "bob"},
%{id: 2, name: "bob"},
%User{id: 1, name: "bob"},
%User{id: 2, name: "bob"},
]}
end
end
Expand All @@ -104,7 +134,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
end

object :user do
meta :scope, User
meta :scope_by, :id

field :id, :integer
field :email, :string
Expand All @@ -116,7 +146,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
end

object :company do
meta :scope, {Company, :user_id}
meta :scope_by, :user_id

field :id, :integer
field :user_id, :integer
Expand All @@ -125,7 +155,7 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
end

object :wallet do
meta :scope, {Wallet, :user_id}
meta :scope_by, :user_id

field :total, :integer
end
Expand Down Expand Up @@ -257,12 +287,18 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
refute Map.has_key?(result, :errors)
end

test "Raises when no meta scope is defined for an object" do
assert_raise RuntimeError, ~r/No meta scope defined for object :not_scoped/, fn ->
test "Raises when no meta scope_by is defined for an object" do
assert_raise RuntimeError, ~r/No meta scope_by defined for object :not_scoped/, fn ->
assert {:ok, _result} = run_pipeline(object_not_scoped_query(2), context(:user, 2))
end
end

test "Raises when returned object is not a struct" do
assert_raise RuntimeError, ~r/Expected a Struct for object :user, got %{id: 2, name: \"bob\"}/, fn ->
assert {:ok, _result} = run_pipeline(object_not_struct_query(2), context(:user, 2))
end
end

defp all_query(id) do
"""
{
Expand Down Expand Up @@ -377,6 +413,17 @@ defmodule Rajska.ObjectScopeAuthorizationTest do
"""
end

defp object_not_struct_query(id) do
"""
{
objectNotStructQuery(id: #{id}) {
name
email
}
}
"""
end

defp context(role, id), do: [context: %{current_user: %{role: role, id: id}}]

defp run_pipeline(document, opts) do
Expand Down
4 changes: 0 additions & 4 deletions test/middlewares/query_scope_authorization_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@ defmodule Rajska.QueryScopeAuthorizationTest do
name: "User",
email: "email@user.com"
]

def __schema__(:source), do: "users"
end

defmodule BankAccount do
defstruct [
id: 1,
total: 5,
]

def __schema__(:source), do: "bank_account"
end

defmodule Authorization do
Expand Down
Loading

0 comments on commit 2fb6003

Please sign in to comment.