Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Commit

Permalink
Refactoring (#26)
Browse files Browse the repository at this point in the history
* Refactor the authorization instrumenter

* Refactor helpers

* Refactor scope instrumenter
  • Loading branch information
phyrog authored Oct 27, 2017
1 parent b62c6c1 commit 3584d57
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 81 deletions.
26 changes: 16 additions & 10 deletions lib/graphql-pundit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@

require 'graphql'

# Define `authorize` and `authorize!` helpers
# Defines authorization related helpers
module GraphQL
# rubocop:disable Metrics/MethodLength
def self.assign_authorize(raise_unauthorized)
# rubocop:enable Metrics/MethodLength
lambda do |defn, query = nil, policy: nil, record: nil|
# Defines `authorize` and `authorize!` helpers
class AuthorizationHelper
attr_reader :raise_unauthorized

def initialize(raise_unauthorized)
@raise_unauthorized = raise_unauthorized
end

def call(defn, query = nil, policy: nil, record: nil)
opts = {record: record,
query: query || defn.name,
policy: policy,
Expand All @@ -23,14 +28,15 @@ def self.assign_authorize(raise_unauthorized)
end
end

def self.assign_scope
lambda do |defn, proc = :infer_scope|
# Defines `scope` helper
class ScopeHelper
def call(defn, proc = :infer_scope)
Define::InstanceDefinable::AssignMetadataKey.new(:scope).
call(defn, proc)
end
end

Field.accepts_definitions(authorize: assign_authorize(false),
authorize!: assign_authorize(true),
scope: assign_scope)
Field.accepts_definitions(authorize: AuthorizationHelper.new(false),
authorize!: AuthorizationHelper.new(true),
scope: ScopeHelper.new)
end
95 changes: 54 additions & 41 deletions lib/graphql-pundit/instrumenters/authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,55 @@ module Pundit
module Instrumenters
# Instrumenter that supplies `authorize`
class Authorization
# This does the actual Pundit authorization
class AuthorizationResolver
attr_reader :current_user, :old_resolver, :options
def initialize(current_user, old_resolver, options)
@current_user = current_user
@old_resolver = old_resolver
@options = options
end

def call(root, arguments, context)
unless authorize(root, arguments, context)
raise ::Pundit::NotAuthorizedError
end
old_resolver.call(root, arguments, context)
rescue ::Pundit::NotAuthorizedError
if options[:raise]
raise GraphQL::ExecutionError, "You're not authorized to do this"
end
end

private

def authorize(root, arguments, context)
if options[:proc]
options[:proc].call(root, arguments, context)
else
record = record(root, arguments, context)
::Pundit::PolicyFinder.new(policy(record)).policy!.
new(context[current_user], record).public_send(query)
end
end

def query
@query ||= options[:query].to_s + '?'
end

def policy(record)
options[:policy] || record
end

def record(root, arguments, context)
if options[:record].respond_to?(:call)
options[:record].call(root, arguments, context)
else
options[:record] || root
end
end
end

attr_reader :current_user

def initialize(current_user = :current_user)
Expand All @@ -15,48 +64,12 @@ def initialize(current_user = :current_user)

def instrument(_type, field)
return field unless field.metadata[:authorize]
old_resolve = field.resolve_proc
resolve_proc = resolve_proc(current_user,
old_resolve,
field.metadata[:authorize])
old_resolver = field.resolve_proc
resolver = AuthorizationResolver.new(current_user,
old_resolver,
field.metadata[:authorize])
field.redefine do
resolve resolve_proc
end
end

# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/BlockLength
def resolve_proc(current_user, old_resolve, options)
lambda do |obj, args, ctx|
begin
result = if options[:proc]
options[:proc].call(obj, args, ctx)
else
query = options[:query].to_s + '?'
record = if options[:record].respond_to?(:call)
options[:record].call(obj, args, ctx)
else
options[:record] || obj
end
policy = options[:policy] || record
policy = ::Pundit::PolicyFinder.new(policy).policy!
policy = policy.new(ctx[current_user], record)
policy.public_send(query)
end
unless result
raise ::Pundit::NotAuthorizedError, query: query,
record: record,
policy: policy
end
old_resolve.call(obj, args, ctx)
rescue ::Pundit::NotAuthorizedError
if options[:raise]
raise GraphQL::ExecutionError,
"You're not authorized to do this"
end
end
resolve resolver
end
end
end
Expand Down
77 changes: 47 additions & 30 deletions lib/graphql-pundit/instrumenters/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,69 @@ module Pundit
module Instrumenters
# Instrumenter that supplies `scope`
class Scope
attr_reader :current_user
# Applies the scoping to the passed object
class ScopeResolver
attr_reader :current_user, :scope, :old_resolver

def initialize(current_user = :current_user)
@current_user = current_user
end
def initialize(current_user, scope, old_resolver)
@current_user = current_user
@old_resolver = old_resolver

# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def instrument(_type, field)
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
scope = field.metadata[:scope]
return field unless scope
unless valid_value?(scope)
raise ArgumentError, 'Invalid value passed to `scope`'
unless valid_value?(scope)
raise ArgumentError, 'Invalid value passed to `scope`'
end

@scope = new_scope(scope)
end

def call(root, arguments, context)
new_scope = scope.call(root, arguments, context)
old_resolver.call(new_scope, arguments, context)
end

old_resolve = field.resolve_proc
private

scope_proc = lambda do |obj, _args, ctx|
unless inferred?(scope)
obj.define_singleton_method(:policy_class) { scope }
def new_scope(scope)
return scope if proc?(scope)

lambda do |root, _arguments, context|
unless inferred?(scope)
root.define_singleton_method(:policy_class) { scope }
end

::Pundit.policy_scope!(context[current_user], root)
end
end

::Pundit.policy_scope!(ctx[current_user], obj)
def valid_value?(value)
value.is_a?(Class) || inferred?(value) || proc?(value)
end
scope_proc = scope if proc?(scope)

field.redefine do
resolve(lambda do |obj, args, ctx|
new_scope = scope_proc.call(obj, args, ctx)
old_resolve.call(new_scope, args, ctx)
end)
def proc?(value)
value.respond_to?(:call)
end

def inferred?(value)
value == :infer_scope
end
end

private
attr_reader :current_user

def valid_value?(value)
value.is_a?(Class) || inferred?(value) || proc?(value)
def initialize(current_user = :current_user)
@current_user = current_user
end

def proc?(value)
value.respond_to?(:call)
end
def instrument(_type, field)
scope = field.metadata[:scope]
return field unless scope

def inferred?(value)
value == :infer_scope
old_resolver = field.resolve_proc
resolver = ScopeResolver.new(current_user, scope, old_resolver)

field.redefine do
resolve resolver
end
end
end
end
Expand Down

0 comments on commit 3584d57

Please sign in to comment.