Skip to content

Commit

Permalink
Merge a7bf4ce into ea92b18
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielpra1 committed Aug 31, 2019
2 parents ea92b18 + a7bf4ce commit 36bd9ec
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 142 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Add your [Authorization](https://hexdocs.pm/rajska/Rajska.Authorization.html) mo
middleware
|> Rajska.add_query_authorization(field, Authorization)
|> Rajska.add_object_authorization()
|> Rajska.add_object_scope_auhtorization()
|> Rajska.add_object_scope_authorization()
end

def middleware(middleware, field, object) do
Expand Down Expand Up @@ -199,7 +199,7 @@ object :wallet do
end
```

To define custom rules for the scoping, use `c:Rajska.Authorization.has_user_access?/3`. For example:
To define custom rules for the scoping, use [has_user_access?/3](https://hexdocs.pm/rajska/Rajska.Authorization.html#c:has_user_access?/3). For example:

```elixir
defmodule Authorization do
Expand Down
9 changes: 5 additions & 4 deletions lib/middlewares/field_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule Rajska.FieldAuthorization do
scope_by = get_scope_by_field!(object, is_field_private?)

resolution
|> Map.get(:context)
|> authorized?(is_field_private?, scope_by, resolution.source)
|> put_result(resolution, field)
end
Expand All @@ -52,12 +53,12 @@ defmodule Rajska.FieldAuthorization do
end
end

defp authorized?(_resolution, false, _scope_by, _source), do: true
defp authorized?(_context, false, _scope_by, _source), do: true

defp authorized?(resolution, true, scope_by, source) do
case Rajska.apply_auth_mod(resolution, :is_super_user?, [resolution]) do
defp authorized?(context, true, scope_by, source) do
case Rajska.apply_auth_mod(context, :is_super_user?, [context]) do
true -> true
false -> Rajska.apply_auth_mod(resolution, :is_resolution_field_authorized?, [resolution, scope_by, source])
false -> Rajska.apply_auth_mod(context, :is_context_field_authorized?, [context, scope_by, source])
end
end

Expand Down
6 changes: 3 additions & 3 deletions lib/middlewares/object_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ defmodule Rajska.ObjectAuthorization do
defp authorize_object(object, fields, resolution) do
object
|> Type.meta(:authorize)
|> is_authorized?(resolution, object)
|> is_authorized?(resolution.context, object)
|> put_result(fields, resolution, object)
end

defp is_authorized?(nil, _, object), do: raise "No meta authorize defined for object #{inspect object.identifier}"

defp is_authorized?(permission, resolution, _object) do
Rajska.apply_auth_mod(resolution, :is_resolution_authorized?, [resolution, permission])
defp is_authorized?(permission, context, _object) do
Rajska.apply_auth_mod(context, :is_context_authorized?, [context, permission])
end

defp put_result(true, fields, resolution, _type), do: find_associations(fields, resolution)
Expand Down
117 changes: 52 additions & 65 deletions lib/middlewares/object_scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,97 +64,84 @@ defmodule Rajska.ObjectScopeAuthorization do
end
```
"""
@behaviour Absinthe.Middleware

alias Absinthe.{
Resolution,
Schema,
Type
}
alias Absinthe.{Blueprint, Phase, Type}
use Absinthe.Phase

def call(%Resolution{definition: definition} = resolution, _config) do
authorize(definition.schema_node.type, definition.selections, resolution)
@spec run(Blueprint.t() | Phase.Error.t(), Keyword.t()) :: {:ok, map}
def run(%Blueprint{execution: execution} = bp, _options \\ []) do
{:ok, %{bp | execution: process(execution)}}
end

defp authorize(type, fields, resolution, nested_keys \\ []) do
type
|> lookup_object(resolution.schema)
|> authorize_object(fields, resolution, nested_keys)
end
defp process(%{validation_errors: [], result: result} = execution), do: %{execution | result: result(result, execution.context)}
defp process(execution), do: execution

# When is a list, inspect object that composes the list.
defp lookup_object(%Type.List{of_type: object_type}, schema) do
lookup_object(object_type, schema)
# Root
defp result(%{fields: fields, emitter: %{schema_node: %{identifier: identifier}}} = result, context)
when identifier in [:query, :mutation, :subscription] do
%{result | fields: field_result(fields, context)}
end

defp lookup_object(object_type, schema) do
Schema.lookup_type(schema, object_type)
end
# Object
defp result(%{fields: fields, emitter: %{schema_node: schema_node} = emitter} = result, context) do
type = get_object_type(schema_node.type)
scope = Type.meta(type, :scope)

# When is a Scalar, Custom or Enum type, authorize.
defp authorize_object(%type{} = object, fields, resolution, nested_keys)
when type in [Scalar, Custom, Type.Enum, Type.Enum.Value] do
put_result(true, fields, resolution, object, nested_keys)
case is_authorized?(scope, result.root_value, context, type) do
true -> %{result | fields: field_result(fields, context)}
false -> Map.put(result, :errors, [error(emitter)])
end
end

# When is an user defined object, lookup the scope meta tag.
defp authorize_object(object, fields, resolution, nested_keys) do
object
|> Type.meta(:scope)
|> is_authorized?(resolution, object, nested_keys)
|> put_result(fields, resolution, object, nested_keys)
# List
defp result(%{values: values} = result, context) do
%{result | values: list_result(values, context)}
end

defp is_authorized?(nil, _, object, _nested_keys), do: raise "No meta scope defined for object #{inspect object.identifier}"
# Leafs
defp result(result, _context), do: result

defp is_authorized?(false, _resolution, _object, _nested_keys), do: true
# When is a list, inspect object that composes the list.
defp get_object_type(%Type.List{of_type: object_type}), do: object_type
defp get_object_type(object_type), do: object_type

defp is_authorized?({scoped_struct, field}, resolution, _object, nested_keys) do
field_keys = nested_keys ++ [field]
apply_authorization!(resolution, scoped_struct, Map.get(resolution, :value), field_keys)
end
defp field_result(fields, context, new_fields \\ [])

defp is_authorized?(scoped_struct, resolution, _object, nested_keys) do
apply_authorization!(resolution, scoped_struct, Map.get(resolution, :value), nested_keys ++ [:id])
end
defp field_result([], _context, new_fields), do: new_fields

defp apply_authorization!(resolution, scoped_struct, values, keys) when is_list(values) do
Enum.all?(values, fn value ->
apply_authorization!(resolution, scoped_struct, value, keys)
end)
defp field_result([field | fields], context, new_fields) do
new_fields = [result(field, context) | new_fields]
field_result(fields, context, new_fields)
end

defp apply_authorization!(resolution, scoped_struct, nil, _keys) do
Rajska.apply_auth_mod(resolution, :has_resolution_access?, [resolution, scoped_struct, nil])
end
defp list_result(values, context, new_values \\ [])

defp apply_authorization!(resolution, scoped_struct, value, [first_key | remaining_keys]) when length(remaining_keys) > 0 do
nested_value = Map.get(value, first_key)
apply_authorization!(resolution, scoped_struct, nested_value, remaining_keys)
end
defp list_result([], _context, new_values), do: new_values

defp apply_authorization!(resolution, scoped_struct, value, [first_key]) do
scoped_field_value = Map.get(value, first_key)
Rajska.apply_auth_mod(resolution, :has_resolution_access?, [resolution, scoped_struct, scoped_field_value])
defp list_result([value | values], context, new_values) do
new_values = [result(value, context) | new_values]
list_result(values, context, new_values)
end

defp put_result(true, fields, resolution, _type, nested_keys), do: find_associations(fields, resolution, nested_keys)
defp is_authorized?(nil, _values, _context, object), do: raise "No meta scope defined for object #{inspect object.identifier}"

defp put_result(false, _fields, resolution, object, _nested_keys) do
Resolution.put_result(resolution, {:error, "Not authorized to access object #{object.identifier}"})
end
defp is_authorized?(false, _values, _context, _object), do: true

defp find_associations([%{selections: []} | tail], resolution, nested_keys) do
find_associations(tail, resolution, nested_keys)
defp is_authorized?({scoped_struct, field}, values, context, _object) do
scoped_field_value = Map.get(values, field)
Rajska.apply_auth_mod(context, :has_context_access?, [context, scoped_struct, scoped_field_value])
end

defp find_associations(
[%{schema_node: schema_node, selections: selections} | tail],
resolution,
nested_keys
) do
authorize(schema_node.type, selections ++ tail, resolution, nested_keys ++ [schema_node.identifier])
defp is_authorized?(scoped_struct, values, context, _object) do
scoped_field_value = Map.get(values, :id)
Rajska.apply_auth_mod(context, :has_context_access?, [context, scoped_struct, scoped_field_value])
end

defp find_associations([], resolution, _nested_keys), do: resolution
defp error(%{source_location: location, schema_node: %{type: type}}) do
%Phase.Error{
phase: __MODULE__,
message: "Not authorized to access object #{get_object_type(type).identifier}",
locations: [location]
}
end
end
16 changes: 8 additions & 8 deletions lib/middlewares/query_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ defmodule Rajska.QueryAuthorization do

@behaviour Absinthe.Middleware

def call(resolution, [{:permit, permission} | _scoped] = config) do
validate_permission!(resolution, permission)
def call(%{context: context} = resolution, [{:permit, permission} | _scoped] = config) do
validate_permission!(context, permission)

resolution
|> Rajska.apply_auth_mod(:is_resolution_authorized?, [resolution, permission])
context
|> Rajska.apply_auth_mod(:is_context_authorized?, [context, permission])
|> update_result(resolution)
|> QueryScopeAuthorization.call(config)
end

defp validate_permission!(resolution, permitted_roles) do
valid_roles = Rajska.apply_auth_mod(resolution, :valid_roles)
defp validate_permission!(context, permitted_roles) do
valid_roles = Rajska.apply_auth_mod(context, :valid_roles)

unless permission_valid?(valid_roles, permitted_roles) do
raise """
Expand All @@ -70,7 +70,7 @@ defmodule Rajska.QueryAuthorization do

defp update_result(true, resolution), do: resolution

defp update_result(false, resolution) do
Resolution.put_result(resolution, {:error, Rajska.apply_auth_mod(resolution, :unauthorized_msg, [resolution])})
defp update_result(false, %{context: context} = resolution) do
Resolution.put_result(resolution, {:error, Rajska.apply_auth_mod(context, :unauthorized_msg, [context])})
end
end
8 changes: 4 additions & 4 deletions lib/middlewares/scope_authorization.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ defmodule Rajska.QueryScopeAuthorization do
def call(resolution, [_ | [scoped: false]]), do: resolution

def call(resolution, [{:permit, permission} | scoped_config]) do
not_scoped_roles = Rajska.apply_auth_mod(resolution, :not_scoped_roles)
not_scoped_roles = Rajska.apply_auth_mod(resolution.context, :not_scoped_roles)

case Enum.member?(not_scoped_roles, permission) do
true -> resolution
Expand Down Expand Up @@ -105,9 +105,9 @@ defmodule Rajska.QueryScopeAuthorization do
raise "Error in query #{name}: no argument found in middleware Scope Authorization"
end

def apply_scope_authorization(resolution, field_value, scoped_struct) do
resolution
|> Rajska.apply_auth_mod(:has_resolution_access?, [resolution, scoped_struct, field_value])
def apply_scope_authorization(%{context: context} = resolution, field_value, scoped_struct) do
context
|> Rajska.apply_auth_mod(:has_context_access?, [context, scoped_struct, field_value])
|> update_result(resolution)
end

Expand Down
28 changes: 13 additions & 15 deletions lib/rajska.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ defmodule Rajska do
Since Scope Authorization middleware must be used with Query Authorization, it is automatically called when adding the former.
"""

alias Absinthe.Resolution

alias Rajska.Authorization

defmacro __using__(opts \\ []) do
Expand All @@ -73,7 +71,7 @@ defmodule Rajska do
Keyword.merge(unquote(opts), [all_role: unquote(all_role), roles: unquote(roles_with_tier)])
end

def get_current_user(%Resolution{context: %{current_user: current_user}}), do: current_user
def get_current_user(%{current_user: current_user}), do: current_user

def get_user_role(%{role: role}), do: role
def get_user_role(nil), do: nil
Expand Down Expand Up @@ -110,28 +108,28 @@ defmodule Rajska do

def unauthorized_msg(_resolution), do: "unauthorized"

def is_super_user?(%Resolution{} = resolution) do
resolution
def is_super_user?(context) do
context
|> get_current_user()
|> get_user_role()
|> is_super_role?()
end

def is_resolution_authorized?(%Resolution{} = resolution, allowed_role) do
resolution
def is_context_authorized?(context, allowed_role) do
context
|> get_current_user()
|> get_user_role()
|> is_role_authorized?(allowed_role)
end

def is_resolution_field_authorized?(%Resolution{} = resolution, scope_by, source) do
resolution
def is_context_field_authorized?(context, scope_by, source) do
context
|> get_current_user()
|> is_field_authorized?(scope_by, source)
end

def has_resolution_access?(%Resolution{} = resolution, scoped_struct, field_value) do
resolution
def has_context_access?(context, scoped_struct, field_value) do
context
|> get_current_user()
|> has_user_access?(scoped_struct, field_value)
end
Expand Down Expand Up @@ -170,18 +168,18 @@ defmodule Rajska do
end

@doc false
def apply_auth_mod(resolution, fnc_name, args \\ [])
def apply_auth_mod(context, fnc_name, args \\ [])

def apply_auth_mod(%Resolution{context: %{authorization: authorization}}, fnc_name, args) do
def apply_auth_mod(%{authorization: authorization}, fnc_name, args) do
apply(authorization, fnc_name, args)
end

def apply_auth_mod(_resolution, _fnc_name, _args) do
def apply_auth_mod(_context, _fnc_name, _args) do
raise "Rajska authorization module not found in Absinthe's context"
end

defdelegate add_query_authorization(middleware, field, authorization), to: Rajska.Schema
defdelegate add_object_authorization(middleware), to: Rajska.Schema
defdelegate add_field_authorization(middleware, field, object), to: Rajska.Schema
defdelegate add_object_scope_auhtorization(middleware), to: Rajska.Schema
defdelegate add_object_scope_authorization(middleware), to: Rajska.Schema
end
4 changes: 2 additions & 2 deletions lib/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ defmodule Rajska.Schema do
[{{FieldAuthorization, :call}, object: object, field: field} | middleware]
end

@spec add_object_scope_auhtorization([Middleware.spec(), ...]) :: [Middleware.spec(), ...]
def add_object_scope_auhtorization(middleware) do
@spec add_object_scope_authorization([Middleware.spec(), ...]) :: [Middleware.spec(), ...]
def add_object_scope_authorization(middleware) do
middleware ++ [ObjectScopeAuthorization]
end

Expand Down
Loading

0 comments on commit 36bd9ec

Please sign in to comment.