graphql-pundit-387 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ #!/usr/bin/env ruby
3
+
4
+ require 'bundler/setup'
5
+ require 'graphql/pundit'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'graphql-pundit/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'graphql-pundit-387'
9
+ spec.version = GraphQL::Pundit::VERSION
10
+ spec.authors = ['Ontohub Core Developers']
11
+ spec.email = ['ontohub-dev-l@ovgu.de']
12
+
13
+ spec.summary = 'Pundit authorization support for graphql'
14
+ spec.description = spec.summary
15
+ spec.homepage = 'https://github.com/ontohub/graphql-pundit'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'graphql', '>= 1.6.4', '< 1.10.0'
26
+ spec.add_dependency 'pundit', '~> 1.1.0'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.14'
29
+ spec.add_development_dependency 'codecov', '~> 0.1.10'
30
+ spec.add_development_dependency 'fuubar', '~> 2.3.0'
31
+ spec.add_development_dependency 'pry', '~> 0.11.0'
32
+ spec.add_development_dependency 'pry-byebug', '~> 3.6.0'
33
+ spec.add_development_dependency 'pry-rescue', '~> 1.4.4'
34
+ spec.add_development_dependency 'pry-stack_explorer', '~> 0.4.9.2'
35
+ spec.add_development_dependency 'rake', '~> 12.0'
36
+ spec.add_development_dependency 'rspec', '~> 3.6'
37
+ spec.add_development_dependency 'rubocop', '~> 0.57.0'
38
+ spec.add_development_dependency 'simplecov', '~> 0.16.1'
39
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql-pundit/instrumenter'
4
+ require 'graphql-pundit/field'
5
+ require 'graphql-pundit/authorization'
6
+ require 'graphql-pundit/scope'
7
+ require 'graphql-pundit/version'
8
+
9
+ require 'graphql'
10
+
11
+ # Defines authorization related helpers
12
+ module GraphQL
13
+ # Defines `authorize` and `authorize!` helpers
14
+ class AuthorizationHelper
15
+ attr_reader :raise_unauthorized
16
+
17
+ def initialize(raise_unauthorized)
18
+ @raise_unauthorized = raise_unauthorized
19
+ end
20
+
21
+ def call(defn, *args, policy: nil, record: nil)
22
+ query = args[0] || defn.name
23
+ opts = {record: record,
24
+ query: query,
25
+ policy: policy,
26
+ raise: raise_unauthorized}
27
+ if query.respond_to?(:call)
28
+ opts = {proc: query, raise: raise_unauthorized}
29
+ end
30
+ Define::InstanceDefinable::AssignMetadataKey.new(:authorize).
31
+ call(defn, opts)
32
+ end
33
+ end
34
+
35
+ # Defines `scope` helper
36
+ class ScopeHelper
37
+ def initialize(before_or_after, deprecated: false)
38
+ @before_or_after = before_or_after
39
+ @deprecated = deprecated
40
+ end
41
+
42
+ def call(defn, proc = :infer_scope)
43
+ opts = {proc: proc, deprecated: @deprecated}
44
+ Define::InstanceDefinable::AssignMetadataKey.
45
+ new(:"#{@before_or_after}_scope").
46
+ call(defn, opts)
47
+ end
48
+ end
49
+
50
+ Field.accepts_definitions(authorize: AuthorizationHelper.new(false),
51
+ authorize!: AuthorizationHelper.new(true),
52
+ after_scope: ScopeHelper.new(:after),
53
+ before_scope: ScopeHelper.new(:before),
54
+ scope: ScopeHelper.new(:before, deprecated: true))
55
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql-pundit/common'
4
+
5
+ module GraphQL
6
+ module Pundit
7
+ # Authorization methods to be included in the used Field class
8
+ module Authorization
9
+ def self.prepended(base)
10
+ base.include(GraphQL::Pundit::Common)
11
+ end
12
+
13
+ # rubocop:disable Metrics/ParameterLists
14
+ def initialize(*args, authorize: nil,
15
+ record: nil,
16
+ policy: nil,
17
+ **kwargs, &block)
18
+ # rubocop:enable Metrics/ParameterLists
19
+ # authorize! is not a valid variable name
20
+ authorize_bang = kwargs.delete(:authorize!)
21
+ @record = record if record
22
+ @policy = policy if policy
23
+ @authorize = authorize_bang || authorize
24
+ @do_raise = !!authorize_bang
25
+ super(*args, **kwargs, &block)
26
+ end
27
+
28
+ def authorize(*args, record: nil, policy: nil)
29
+ @authorize = args[0] || true
30
+ @record = record if record
31
+ @policy = policy if policy
32
+ end
33
+
34
+ def authorize!(*args, record: nil, policy: nil)
35
+ @do_raise = true
36
+ authorize(*args, record: record, policy: policy)
37
+ end
38
+
39
+ def resolve_field(obj, args, ctx)
40
+ raise ::Pundit::NotAuthorizedError unless do_authorize(obj, args, ctx)
41
+ super(obj, args, ctx)
42
+ rescue ::Pundit::NotAuthorizedError
43
+ if @do_raise
44
+ raise GraphQL::ExecutionError, "You're not authorized to do this"
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def do_authorize(root, arguments, context)
51
+ return true unless @authorize
52
+ return @authorize.call(root, arguments, context) if callable? @authorize
53
+
54
+ query = infer_query(@authorize)
55
+ record = infer_record(@record, root, arguments, context)
56
+ policy = infer_policy(@policy, record, arguments, context)
57
+
58
+ policy.new(context[self.class.current_user], record).public_send query
59
+ end
60
+
61
+ def infer_query(auth_value)
62
+ # authorize can be callable, true (for inference) or a policy query
63
+ query = auth_value.equal?(true) ? method_sym : auth_value
64
+ query.to_s + '?'
65
+ end
66
+
67
+ def infer_record(record, root, arguments, context)
68
+ # record can be callable, nil (for inference) or just any other value
69
+ if callable?(record)
70
+ record.call(root, arguments, context)
71
+ elsif record.equal?(nil)
72
+ root
73
+ else
74
+ record
75
+ end
76
+ end
77
+
78
+ def infer_policy(policy, record, arguments, context)
79
+ # policy can be callable, nil (for inference) or a policy class
80
+ if callable?(policy)
81
+ policy.call(record, arguments, context)
82
+ elsif policy.equal?(nil)
83
+ infer_from = model?(record) ? record.model : record
84
+ ::Pundit::PolicyFinder.new(infer_from).policy!
85
+ else
86
+ policy
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Pundit
5
+ # Common methods used for authorization and scopes
6
+ module Common
7
+ # Class methods to be included in the Field class
8
+ module ClassMethods
9
+ def current_user(current_user = nil)
10
+ return @current_user unless current_user
11
+ @current_user = current_user
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ @current_user = :current_user
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ def callable?(thing)
21
+ thing.respond_to?(:call)
22
+ end
23
+
24
+ def model?(thing)
25
+ thing.respond_to?(:model)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require 'graphql-pundit/authorization'
5
+ require 'graphql-pundit/scope'
6
+
7
+ module GraphQL
8
+ module Pundit
9
+ if defined?(GraphQL::Schema::Field)
10
+ # Field class that contains authorization and scope behavior
11
+ # This only works with graphql >= 1.8.0
12
+ class Field < GraphQL::Schema::Field
13
+ # prepend GraphQL::Pundit::Scope
14
+ prepend GraphQL::Pundit::Authorization
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pundit'
4
+ require 'graphql-pundit/instrumenters/authorization'
5
+ require 'graphql-pundit/instrumenters/before_scope'
6
+ require 'graphql-pundit/instrumenters/after_scope'
7
+
8
+ module GraphQL
9
+ module Pundit
10
+ # Intrumenter combining the authorization and scope instrumenters
11
+ class Instrumenter
12
+ attr_reader :current_user,
13
+ :authorization_instrumenter,
14
+ :before_scope_instrumenter,
15
+ :after_scope_instrumenter
16
+
17
+ def initialize(current_user = :current_user)
18
+ @current_user = current_user
19
+ @authorization_instrumenter =
20
+ Instrumenters::Authorization.new(current_user)
21
+ @before_scope_instrumenter =
22
+ Instrumenters::BeforeScope.new(current_user)
23
+ @after_scope_instrumenter = Instrumenters::AfterScope.new(current_user)
24
+ end
25
+
26
+ def instrument(type, field)
27
+ before_scoped_field = before_scope_instrumenter.instrument(type, field)
28
+ after_scoped_field = after_scope_instrumenter.
29
+ instrument(type, before_scoped_field)
30
+ authorization_instrumenter.instrument(type, after_scoped_field)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pundit'
4
+ require_relative 'scope'
5
+
6
+ module GraphQL
7
+ module Pundit
8
+ module Instrumenters
9
+ # Instrumenter that supplies `after_scope`
10
+ class AfterScope < Scope
11
+ SCOPE_KEY = :after_scope
12
+
13
+ # Applies the scoping to the passed object
14
+ class ScopeResolver < ScopeResolver
15
+ def call(root, arguments, context)
16
+ resolver_result = old_resolver.call(root, arguments, context)
17
+ scope_proc = new_scope(scope)
18
+ scope_proc.call(resolver_result, arguments, context)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pundit'
4
+
5
+ module GraphQL
6
+ module Pundit
7
+ module Instrumenters
8
+ # Instrumenter that supplies `authorize`
9
+ class Authorization
10
+ # This does the actual Pundit authorization
11
+ class AuthorizationResolver
12
+ attr_reader :current_user, :old_resolver, :options
13
+ def initialize(current_user, old_resolver, options)
14
+ @current_user = current_user
15
+ @old_resolver = old_resolver
16
+ @options = options
17
+ end
18
+
19
+ def call(root, arguments, context)
20
+ unless authorize(root, arguments, context)
21
+ raise ::Pundit::NotAuthorizedError
22
+ end
23
+ old_resolver.call(root, arguments, context)
24
+ rescue ::Pundit::NotAuthorizedError
25
+ if options[:raise]
26
+ raise GraphQL::ExecutionError, "You're not authorized to do this"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def authorize(root, arguments, context)
33
+ if options[:proc]
34
+ options[:proc].call(root, arguments, context)
35
+ else
36
+ record = record(root, arguments, context)
37
+ ::Pundit::PolicyFinder.new(policy(record)).policy!.
38
+ new(context[current_user], record).public_send(query)
39
+ end
40
+ end
41
+
42
+ def query
43
+ @query ||= options[:query].to_s + '?'
44
+ end
45
+
46
+ def policy(record)
47
+ options[:policy] || record
48
+ end
49
+
50
+ def record(root, arguments, context)
51
+ if options[:record].respond_to?(:call)
52
+ options[:record].call(root, arguments, context)
53
+ else
54
+ options[:record] || root
55
+ end
56
+ end
57
+ end
58
+
59
+ attr_reader :current_user
60
+
61
+ def initialize(current_user = :current_user)
62
+ @current_user = current_user
63
+ end
64
+
65
+ def instrument(_type, field)
66
+ return field unless field.metadata[:authorize]
67
+ old_resolver = field.resolve_proc
68
+ resolver = AuthorizationResolver.new(current_user,
69
+ old_resolver,
70
+ field.metadata[:authorize])
71
+ field.redefine do
72
+ resolve resolver
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pundit'
4
+ require_relative 'scope'
5
+
6
+ module GraphQL
7
+ module Pundit
8
+ module Instrumenters
9
+ # Instrumenter that supplies `before_scope`
10
+ class BeforeScope < Scope
11
+ SCOPE_KEY = :before_scope
12
+
13
+ # Applies the scoping to the passed object
14
+ class ScopeResolver < ScopeResolver
15
+ def call(root, arguments, context)
16
+ if field.metadata[:before_scope][:deprecated]
17
+ Kernel.warn <<~DEPRECATION_WARNING
18
+ Using `scope` is deprecated and might be removed in the future.
19
+ Please use `before_scope` or `after_scope` instead.
20
+ DEPRECATION_WARNING
21
+ end
22
+ scope_proc = new_scope(scope)
23
+ resolver_result = scope_proc.call(root, arguments, context)
24
+ old_resolver.call(resolver_result, arguments, context)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pundit'
4
+
5
+ module GraphQL
6
+ module Pundit
7
+ module Instrumenters
8
+ # Base instrumenter for `before_scope` and `after_scope`
9
+ class Scope
10
+ # Applies the scoping to the passed object
11
+ class ScopeResolver
12
+ attr_reader :current_user, :scope, :old_resolver, :field
13
+
14
+ def initialize(current_user, scope, old_resolver, field)
15
+ @current_user = current_user
16
+ @old_resolver = old_resolver
17
+ @field = field
18
+
19
+ unless valid_value?(scope)
20
+ raise ArgumentError, 'Invalid value passed to `scope`'
21
+ end
22
+
23
+ @scope = scope
24
+ end
25
+
26
+ private
27
+
28
+ def new_scope(scope)
29
+ return scope if proc?(scope)
30
+
31
+ lambda do |root, _arguments, context|
32
+ scope = find_scope(root, scope)
33
+ scope.new(context[current_user], root).resolve
34
+ end
35
+ end
36
+
37
+ def find_scope(root, scope)
38
+ if !inferred?(scope)
39
+ scope::Scope
40
+ else
41
+ # Special case for Sequel datasets that do not respond to
42
+ # ActiveModel's model_name
43
+ infer_from = if root.respond_to?(:model)
44
+ root.model
45
+ else
46
+ root
47
+ end
48
+ ::Pundit::PolicyFinder.new(infer_from).scope!
49
+ end
50
+ end
51
+
52
+ def valid_value?(value)
53
+ value.is_a?(Class) || inferred?(value) || proc?(value)
54
+ end
55
+
56
+ def proc?(value)
57
+ value.respond_to?(:call)
58
+ end
59
+
60
+ def inferred?(value)
61
+ value == :infer_scope
62
+ end
63
+ end
64
+
65
+ attr_reader :current_user
66
+
67
+ def initialize(current_user = :current_user)
68
+ @current_user = current_user
69
+ end
70
+
71
+ # rubocop:disable Metrics/MethodLength
72
+ def instrument(_type, field)
73
+ # rubocop:enable Metrics/MethodLength
74
+ scope_metadata = field.metadata[self.class::SCOPE_KEY]
75
+ return field unless scope_metadata
76
+ scope = scope_metadata[:proc]
77
+
78
+ old_resolver = field.resolve_proc
79
+ resolver = self.class::ScopeResolver.new(current_user,
80
+ scope,
81
+ old_resolver,
82
+ field)
83
+
84
+ field.redefine do
85
+ resolve resolver
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end