graphql-pundit-387 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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