Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
exAspArk committed Apr 6, 2020
1 parent 7ac7c6e commit a8312e0
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 45 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ end
</pre>

With `graphql-ruby` gem version >= 1.8 and class-based type definitions, use `camelCased` field names in the policy object.
You'd also need to use `type.metadata` (related to [rmosolgo/graphql-ruby#1429](https://github.com/rmosolgo/graphql-ruby/issues/1429)) to get the type class:
You'd also need to use `type_class` (related to [rmosolgo/graphql-ruby#1429](https://github.com/rmosolgo/graphql-ruby/issues/1429)):

<pre>
class GraphqlPolicy
Expand All @@ -136,7 +136,8 @@ class GraphqlPolicy
}

def self.guard(type, field)
RULES.dig(<b>type.metadata[:type_class]</b>, field)
type_class = type.respond_to?(:type_class) ? type.type_class : type.metadata[:type_class]
RULES.dig(<b>type_class</b>, field)
end
end
</pre>
Expand Down
51 changes: 22 additions & 29 deletions lib/graphql/guard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
module GraphQL
class Guard
ANY_FIELD_NAME = :'*'
DEFAULT_NOT_AUTHORIZED = ->(type, field) { raise NotAuthorizedError.new("Not authorized to access: #{type}.#{field}") }
DEFAULT_NOT_AUTHORIZED = ->(type, field) do
type_name = type.respond_to?(:graphql_definition) ? type.graphql_definition : type
raise NotAuthorizedError.new("Not authorized to access: #{type_name}.#{field}")
end

NotAuthorizedError = Class.new(StandardError)

Expand All @@ -18,33 +21,27 @@ def initialize(policy_object: nil, not_authorized: DEFAULT_NOT_AUTHORIZED)
end

def use(schema_definition)
schema_definition.instrument(:field, self)
add_schema_masking!(schema_definition)
end

def instrument(type, field)
guard_proc = guard_proc(type, field)
return field unless guard_proc
if schema_definition.respond_to?(:interpreter?) && schema_definition.interpreter?
require "graphql/guard/field_extension"

old_resolve_proc = field.resolve_proc
new_resolve_proc = ->(object, arguments, context) do
authorized = guard_proc.call(object, arguments, context)

if authorized
old_resolve_proc.call(object, arguments, context)
else
not_authorized.call(type, field.name.to_sym)
schema_definition.query.fields.each do |name, field|
field_class = field.respond_to?(:type_class) ? field.type_class : field.metadata[:type_class]
field_class.extension(GraphQL::Guard::FieldExtension, guard_instance: self)
end
end
else
require "graphql/guard/field_instrumentation"

field.redefine { resolve(new_resolve_proc) }
field_instrumentation = GraphQL::Guard::FieldInstrumentation.new(guard_instance: self)
schema_definition.instrument(:field, field_instrumentation)
add_schema_masking!(schema_definition)
end
end

def guard_proc(type, field)
inline_field_guard(field) ||
def find_guard_proc(type, field)
inline_guard(field) ||
policy_object_guard(type, field.name.to_sym) ||
inline_type_guard(type) ||
policy_object_guard(type, ANY_FIELD_NAME)
inline_guard(type) ||
policy_object_guard(type, GraphQL::Guard::ANY_FIELD_NAME)
end

private
Expand All @@ -66,15 +63,11 @@ def default_filter
end

def policy_object_guard(type, field_name)
policy_object && policy_object.guard(type, field_name)
end

def inline_field_guard(field)
field.metadata[:guard]
@policy_object && @policy_object.guard(type, field_name)
end

def inline_type_guard(type)
type.metadata[:guard]
def inline_guard(type_or_field)
(type_or_field.respond_to?(:graphql_definition) ? type_or_field.graphql_definition : type_or_field).metadata[:guard]
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions lib/graphql/guard/field_extension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module GraphQL
class Guard
class FieldExtension < GraphQL::Schema::FieldExtension
def resolve(object:, arguments:, **rest)
guard_proc = options[:guard_instance].find_guard_proc(field.owner, field)
return field unless guard_proc

authorized = guard_proc.call(object, arguments, rest[:context])

if authorized
yield(object, arguments)
else
options[:guard_instance].not_authorized.call(field.owner, field.name.to_sym)
end
end
end
end
end
27 changes: 27 additions & 0 deletions lib/graphql/guard/field_instrumentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module GraphQL
class Guard
class FieldInstrumentation
def initialize(guard_instance:)
@guard_instance = guard_instance
end

def instrument(type, field)
guard_proc = @guard_instance.find_guard_proc(type, field)
return field unless guard_proc

old_resolve_proc = field.resolve_proc
new_resolve_proc = ->(object, arguments, context) do
authorized = guard_proc.call(object, arguments, context)

if authorized
old_resolve_proc.call(object, arguments, context)
else
@guard_instance.not_authorized.call(type, field.name.to_sym)
end
end

field.redefine { resolve(new_resolve_proc) }
end
end
end
end
18 changes: 7 additions & 11 deletions lib/graphql/guard/testing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ class Field
NoGuardError = Class.new(StandardError)

def guard(*args)
raise NoGuardError.new("Get your field by calling: Type.field_with_guard('#{name}')") unless @__guard_type
guard_proc = @__guard_object.guard_proc(@__guard_type, self)
raise NoGuardError.new("Get your field by calling: Type.field_with_guard('#{name}')") unless @__guard_instance

guard_proc = @__guard_instance.find_guard_proc(@__guard_type, self)
raise NoGuardError.new("Guard lambda does not exist for #{@__guard_type}.#{name}") unless guard_proc

guard_proc.call(*args)
end

def __policy_object=(policy_object)
def __set_guard_instance(policy_object, guard_type)
@__policy_object = policy_object
@__guard_object = GraphQL::Guard.new(policy_object: policy_object)
end

def __guard_type=(guard_type)
@__guard_type = guard_type
@__guard_instance = GraphQL::Guard.new(policy_object: policy_object)
end
end

Expand All @@ -28,8 +26,7 @@ def field_with_guard(field_name, policy_object = nil)
return unless field

field.clone.tap do |f|
f.__policy_object = policy_object
f.__guard_type = self
f.__set_guard_instance(policy_object, self)
end
end
end
Expand All @@ -41,8 +38,7 @@ def self.field_with_guard(field_name, policy_object = nil)
return unless field

field.to_graphql.clone.tap do |f|
f.__policy_object = policy_object
f.__guard_type = self.to_graphql
f.__set_guard_instance(policy_object, self.to_graphql)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/inline_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class PostType < GraphQL::Schema::Object
class QueryType < GraphQL::Schema::Object
field :posts, [PostType], null: false do
argument :user_id, ID, required: true
guard ->(_obj, args, ctx) { args[:userId] == ctx[:current_user].id }
guard ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
end

field :posts_with_mask, [PostType], null: false do
Expand Down
5 changes: 3 additions & 2 deletions spec/fixtures/policy_object_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ def posts(user_id:)
class GraphqlPolicy
RULES = {
QueryType => {
posts: ->(_obj, args, ctx) { args[:userId] == ctx[:current_user].id }
posts: ->(_obj, args, ctx) { args[:user_id] == ctx[:current_user].id }
},
PostType => {
'*': ->(_post, args, ctx) { ctx[:current_user].admin? }
}
}

def self.guard(type, field)
RULES.dig(type.metadata[:type_class], field)
type_class = type.respond_to?(:type_class) ? type.type_class : type.metadata[:type_class]
RULES.dig(type_class, field)
end
end

Expand Down

0 comments on commit a8312e0

Please sign in to comment.