rails-graphql 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/rails/graphql.rb +1 -3
- data/lib/rails/graphql/callback.rb +23 -13
- data/lib/rails/graphql/errors.rb +4 -0
- data/lib/rails/graphql/event.rb +16 -1
- data/lib/rails/graphql/field.rb +9 -4
- data/lib/rails/graphql/field/authorized_field.rb +33 -0
- data/lib/rails/graphql/field/input_field.rb +11 -1
- data/lib/rails/graphql/field/mutation_field.rb +4 -3
- data/lib/rails/graphql/field/output_field.rb +4 -0
- data/lib/rails/graphql/field/proxied_field.rb +5 -0
- data/lib/rails/graphql/field/resolved_field.rb +4 -4
- data/lib/rails/graphql/field/scoped_config.rb +1 -1
- data/lib/rails/graphql/helpers/with_assignment.rb +4 -2
- data/lib/rails/graphql/helpers/with_callbacks.rb +1 -1
- data/lib/rails/graphql/helpers/with_events.rb +8 -8
- data/lib/rails/graphql/helpers/with_owner.rb +10 -2
- data/lib/rails/graphql/helpers/with_schema_fields.rb +5 -1
- data/lib/rails/graphql/railties/controller.rb +1 -1
- data/lib/rails/graphql/request.rb +7 -3
- data/lib/rails/graphql/request/component/field.rb +9 -4
- data/lib/rails/graphql/request/errors.rb +2 -2
- data/lib/rails/graphql/request/event.rb +4 -4
- data/lib/rails/graphql/request/helpers/directives.rb +3 -2
- data/lib/rails/graphql/request/steps/authorizable.rb +101 -0
- data/lib/rails/graphql/request/strategy.rb +1 -1
- data/lib/rails/graphql/source.rb +5 -0
- data/lib/rails/graphql/source/active_record/builders.rb +1 -1
- data/lib/rails/graphql/source/active_record_source.rb +11 -6
- data/lib/rails/graphql/source/scoped_arguments.rb +1 -1
- data/lib/rails/graphql/type_map.rb +4 -4
- data/lib/rails/graphql/version.rb +1 -1
- data/test/integration/authorization/authorization_test.rb +112 -0
- data/test/integration/schemas/authorization.rb +12 -0
- data/test/test_ext.rb +14 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f478dece4e018e58e675abb2f4fbff2edb9ad6279db0efae74037f4ed6c7479a
|
4
|
+
data.tar.gz: dc9d08df7234d4d5a833259f3feb95bfc088ed18201b43ebe70c2c62c76b5b40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f532b284a2e9af983f8406d570b3b0dabaf017d588de1c211709536a7fbca3450d5ca0fc9850ffb5fd191e022d989b900111be1a347db41abfb40b5958f05cb1
|
7
|
+
data.tar.gz: 2465ad648d17fc618545fef5931c12add940805c35733d2424589c50dc14d2e9c4ae4620613ba8fc3f3e46eaaa34d1524fd9b18a2bb4ff05a0fc7b02139dd0d3
|
data/lib/rails/graphql.rb
CHANGED
@@ -136,9 +136,7 @@ module Rails # :nodoc:
|
|
136
136
|
|
137
137
|
if (source = xargs.delete(:source)).present?
|
138
138
|
location = xargs.delete(:location) || source.try(:directive_location)
|
139
|
-
event ||= GraphQL::Event.new(:attach, source, **xargs
|
140
|
-
phase: :definition,
|
141
|
-
))
|
139
|
+
event ||= GraphQL::Event.new(:attach, source, phase: :definition, **xargs)
|
142
140
|
end
|
143
141
|
|
144
142
|
Array.wrap(list).each_with_object(Set.new) do |item, result|
|
@@ -15,7 +15,7 @@ module Rails # :nodoc:
|
|
15
15
|
|
16
16
|
# Directives need to be contextualized by the given instance as +context+
|
17
17
|
def self.set_context(item, context)
|
18
|
-
lambda { |*args| item.call(*args,
|
18
|
+
lambda { |*args, **xargs| item.call(*args, _callback_context: context, **xargs) }
|
19
19
|
end
|
20
20
|
|
21
21
|
def initialize(target, event_name, *args, **xargs, &block)
|
@@ -46,9 +46,12 @@ module Rails # :nodoc:
|
|
46
46
|
|
47
47
|
# This does the whole checking and preparation in order to really execute
|
48
48
|
# the callback method
|
49
|
-
def call(event,
|
49
|
+
def call(event, *args, _callback_context: nil, **xargs)
|
50
50
|
return unless event.name === event_name && can_run?(event)
|
51
|
-
|
51
|
+
|
52
|
+
block.is_a?(Symbol) \
|
53
|
+
? call_symbol(event, *args, **xargs) \
|
54
|
+
: call_proc(event, _callback_context, *args, **xargs)
|
52
55
|
end
|
53
56
|
|
54
57
|
# Get a described source location for the callback
|
@@ -68,38 +71,45 @@ module Rails # :nodoc:
|
|
68
71
|
|
69
72
|
private
|
70
73
|
|
74
|
+
# Find the proper owner of the symbol based callback
|
75
|
+
def owner
|
76
|
+
@owner ||= target.all_owners.find do |item|
|
77
|
+
item.is_a?(Class) ? item.method_defined?(block) : item.respond_to?(block)
|
78
|
+
end || target
|
79
|
+
end
|
80
|
+
|
71
81
|
# Using the filters, check if the current callback can be executed
|
72
82
|
def can_run?(event)
|
73
83
|
filters.all? { |key, options| event_filters[key][:block].call(options, event) }
|
74
84
|
end
|
75
85
|
|
76
86
|
# Call the callback block as a symbol
|
77
|
-
def call_symbol(event)
|
78
|
-
owner = target.try(:proxied_owner) || target.try(:owner) || target
|
87
|
+
def call_symbol(event, *args, **xargs)
|
79
88
|
event.on_instance(owner) do |instance|
|
80
89
|
block = instance.method(@block)
|
81
|
-
args, xargs = collect_parameters(event, block)
|
90
|
+
args, xargs = collect_parameters(event, [args, xargs], block)
|
82
91
|
block.call(*args, **xargs)
|
83
92
|
end
|
84
93
|
end
|
85
94
|
|
86
95
|
# Call the callback block as a proc
|
87
|
-
def call_proc(event, context = nil)
|
88
|
-
args, xargs = collect_parameters(event)
|
96
|
+
def call_proc(event, context = nil, *args, **xargs)
|
97
|
+
args, xargs = collect_parameters(event, [args, xargs])
|
89
98
|
(context || event).instance_exec(*args, **xargs, &block)
|
90
99
|
end
|
91
100
|
|
92
101
|
# Read the arguments needed for a block then collect them from the
|
93
102
|
# event and return the execution args
|
94
|
-
def collect_parameters(event, block = @block)
|
95
|
-
args_source = event.send(:args_source)
|
96
|
-
|
97
|
-
|
103
|
+
def collect_parameters(event, send_args, block = @block)
|
104
|
+
args_source = event.send(:args_source)
|
105
|
+
send_args[0] += @pre_args.deep_dup
|
106
|
+
send_args[1].merge!(@pre_xargs.deep_dup)
|
107
|
+
return send_args unless inject_arguments?
|
98
108
|
|
99
109
|
# TODO: Maybe we need to turn procs into lambdas so the optional
|
100
110
|
# arguments doesn't suffer any kind of change
|
101
111
|
idx = -1
|
102
|
-
block.parameters.each_with_object(
|
112
|
+
block.parameters.each_with_object(send_args) do |(type, name), result|
|
103
113
|
case type
|
104
114
|
when :opt, :req
|
105
115
|
idx += 1
|
data/lib/rails/graphql/errors.rb
CHANGED
@@ -38,5 +38,9 @@ module Rails # :nodoc:
|
|
38
38
|
# Error class related to when the captured output value is invalid due to
|
39
39
|
# type checking
|
40
40
|
InvalidValueError = Class.new(FieldError)
|
41
|
+
|
42
|
+
# Error class related to when a field is unauthorized and can not be used,
|
43
|
+
# similar to disabled fields
|
44
|
+
UnauthorizedFieldError = Class.new(FieldError)
|
41
45
|
end
|
42
46
|
end
|
data/lib/rails/graphql/event.rb
CHANGED
@@ -108,7 +108,7 @@ module Rails # :nodoc:
|
|
108
108
|
|
109
109
|
# Call a given block and send the event as reference
|
110
110
|
def trigger(block)
|
111
|
-
catchable(:item) {
|
111
|
+
@last_result = catchable(:item) { block.call(self) }
|
112
112
|
end
|
113
113
|
|
114
114
|
# Stop the execution of an event using a given +layer+. The default is to
|
@@ -127,6 +127,10 @@ module Rails # :nodoc:
|
|
127
127
|
|
128
128
|
alias call_super call_next
|
129
129
|
|
130
|
+
protected
|
131
|
+
|
132
|
+
alias args_source itself
|
133
|
+
|
130
134
|
private
|
131
135
|
|
132
136
|
# Add the layer, exec the block and remove the layer
|
@@ -136,6 +140,17 @@ module Rails # :nodoc:
|
|
136
140
|
ensure
|
137
141
|
@layers.pop
|
138
142
|
end
|
143
|
+
|
144
|
+
# Check for data based readers
|
145
|
+
def respond_to_missing?(method_name, include_private = false)
|
146
|
+
data.key?(method_name) || super
|
147
|
+
end
|
148
|
+
|
149
|
+
# If the +method_name+ matches any key entry of the provided data, just
|
150
|
+
# return the value stored there
|
151
|
+
def method_missing(method_name, *)
|
152
|
+
data.key?(method_name) ? data[method_name] : super
|
153
|
+
end
|
139
154
|
end
|
140
155
|
end
|
141
156
|
end
|
data/lib/rails/graphql/field.rb
CHANGED
@@ -38,20 +38,20 @@ module Rails # :nodoc:
|
|
38
38
|
|
39
39
|
autoload :ScopedConfig
|
40
40
|
|
41
|
+
autoload :AuthorizedField
|
42
|
+
autoload :ProxiedField
|
41
43
|
autoload :ResolvedField
|
42
44
|
autoload :TypedField
|
43
|
-
autoload :ProxiedField
|
44
45
|
|
45
46
|
autoload :InputField
|
46
47
|
autoload :OutputField
|
47
48
|
autoload :MutationField
|
48
49
|
|
49
|
-
|
50
|
+
attr_reader :name, :gql_name, :owner
|
50
51
|
|
52
|
+
delegate :input_type?, :output_type?, :leaf_type?, :proxy?, :mutation?, to: :class
|
51
53
|
delegate :namespaces, to: :owner
|
52
54
|
|
53
|
-
attr_reader :name, :gql_name, :owner
|
54
|
-
|
55
55
|
class << self
|
56
56
|
# A small shared helper method that allows field information to be
|
57
57
|
# proxied
|
@@ -145,6 +145,11 @@ module Rails # :nodoc:
|
|
145
145
|
(other.nullable? == nullable? || other.nullable? && !nullable?)
|
146
146
|
end
|
147
147
|
|
148
|
+
# Return the owner as the single item of the list
|
149
|
+
def all_owners
|
150
|
+
[owner]
|
151
|
+
end
|
152
|
+
|
148
153
|
# Checks if the argument can be null
|
149
154
|
def null?
|
150
155
|
!!@null
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
# This provides ways for fields to be authorized, giving a logical level for
|
6
|
+
# enabling or disabling access to a field. It has a similar structure to
|
7
|
+
# events, but has a different hierarchy of resolution
|
8
|
+
module Field::AuthorizedField
|
9
|
+
# Just add the callbacks setup to the field
|
10
|
+
def self.included(other)
|
11
|
+
other.event_types(:authorize, append: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add either settings for authorization or a block to be executed. It
|
15
|
+
# returns +self+ for chain purposes
|
16
|
+
def authorize(*args, **xargs, &block)
|
17
|
+
@authorizer = [args, xargs, block]
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the settings for the authorize process
|
22
|
+
def authorizer
|
23
|
+
@authorizer if authorizable?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks if the field should go through an authorization process
|
27
|
+
def authorizable?
|
28
|
+
defined?(@authorizer)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
#
|
@@ -55,7 +55,17 @@ module Rails # :nodoc:
|
|
55
55
|
|
56
56
|
# Return the default value if the given +value+ is nil
|
57
57
|
def deserialize(value = nil)
|
58
|
-
value.nil? ?
|
58
|
+
value.nil? ? default : super
|
59
|
+
end
|
60
|
+
|
61
|
+
# A little override to use the default value
|
62
|
+
def to_json(value = nil)
|
63
|
+
super(value.nil? ? default : value)
|
64
|
+
end
|
65
|
+
|
66
|
+
# A little override to use the default value
|
67
|
+
def as_json(value = nil)
|
68
|
+
super(value.nil? ? default : value)
|
59
69
|
end
|
60
70
|
|
61
71
|
# Checks if the default value of the field is valid
|
@@ -16,16 +16,17 @@ module Rails # :nodoc:
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# Add a block or a callable method that is executed before the resolver
|
19
|
-
# but after all the before resolve
|
19
|
+
# but after all the before resolve. It returns +self+ for chain purposes
|
20
20
|
def perform(*args, **xargs, &block)
|
21
21
|
@performer = Callback.new(self, :perform, *args, **xargs, &block)
|
22
|
+
self
|
22
23
|
end
|
23
24
|
|
24
25
|
# Get the performer that can be already defined or used through the
|
25
26
|
# +method_name+ if that is callable
|
26
27
|
def performer
|
27
|
-
@performer ||= callable?(method_name) \
|
28
|
-
? Callback.new(self, :perform, method_name) \
|
28
|
+
@performer ||= callable?(:"#{method_name}!") \
|
29
|
+
? Callback.new(self, :perform, :"#{method_name}!") \
|
29
30
|
: false
|
30
31
|
end
|
31
32
|
|
@@ -10,6 +10,10 @@ module Rails # :nodoc:
|
|
10
10
|
class Field::OutputField < Field
|
11
11
|
include Helpers::WithArguments
|
12
12
|
include Helpers::WithValidator
|
13
|
+
include Helpers::WithEvents
|
14
|
+
include Helpers::WithCallbacks
|
15
|
+
|
16
|
+
include Field::AuthorizedField
|
13
17
|
include Field::ResolvedField
|
14
18
|
include Field::TypedField
|
15
19
|
|
@@ -29,16 +29,16 @@ module Rails # :nodoc:
|
|
29
29
|
|
30
30
|
# Just add the callbacks setup to the field
|
31
31
|
def self.included(other)
|
32
|
-
other.
|
33
|
-
other.include(Helpers::WithCallbacks)
|
34
|
-
other.event_types(:prepare, :finalize, expose: true)
|
32
|
+
other.event_types(:prepare, :finalize, append: true, expose: true)
|
35
33
|
other.alias_method(:before_resolve, :prepare)
|
36
34
|
other.alias_method(:after_resolve, :finalize)
|
37
35
|
end
|
38
36
|
|
39
|
-
# Add a block that is performed while resolving a value of a field
|
37
|
+
# Add a block that is performed while resolving a value of a field. It
|
38
|
+
# returns +self+ for chain purposes
|
40
39
|
def resolve(*args, **xargs, &block)
|
41
40
|
@resolver = Callback.new(self, :resolve, *args, **xargs, &block)
|
41
|
+
self
|
42
42
|
end
|
43
43
|
|
44
44
|
# Get the resolver that can be already defined or used through the
|
@@ -6,7 +6,7 @@ module Rails # :nodoc:
|
|
6
6
|
# instance of this class works as proxy for changes to the actual field.
|
7
7
|
Field::ScopedConfig = Struct.new(:field, :receiver) do
|
8
8
|
delegate :argument, :ref_argument, :id_argument, :use, :internal?, :disabled?,
|
9
|
-
:enabled?, :disable!, :enable!, to: :field
|
9
|
+
:enabled?, :disable!, :enable!, :authorize, to: :field
|
10
10
|
|
11
11
|
delegate_missing_to :receiver
|
12
12
|
|
@@ -54,14 +54,16 @@ module Rails # :nodoc:
|
|
54
54
|
# Ignores the possible errors here related
|
55
55
|
end
|
56
56
|
|
57
|
-
# After a successfully registration, add the assigned class to the
|
58
|
-
#
|
57
|
+
# After a successfully registration, add the assigned class to the type
|
58
|
+
# map as a great alias to find the object, but only if the class does
|
59
|
+
# not have an alias already
|
59
60
|
def register!
|
60
61
|
return if abstract?
|
61
62
|
return super unless assigned?
|
62
63
|
|
63
64
|
result = super
|
64
65
|
return result unless (klass = safe_assigned_class)
|
66
|
+
return result if GraphQL.type_map.exist?(klass, namespaces: namespaces)
|
65
67
|
|
66
68
|
GraphQL.type_map.register_alias(klass, namespaces: namespaces, &method(:itself))
|
67
69
|
result
|
@@ -9,7 +9,7 @@ module Rails # :nodoc:
|
|
9
9
|
# symbolic methods
|
10
10
|
module WithCallbacks
|
11
11
|
DEFAULT_EVENT_TYPES = %i[query mutation subscription request attach
|
12
|
-
organized prepared finalize].freeze
|
12
|
+
authorize organized prepared finalize].freeze
|
13
13
|
|
14
14
|
def self.extended(other)
|
15
15
|
other.extend(WithCallbacks::Setup)
|
@@ -27,20 +27,20 @@ module Rails # :nodoc:
|
|
27
27
|
# Set or get the list of possible event types when attaching events
|
28
28
|
def event_types(*list, append: false, expose: false)
|
29
29
|
return (defined?(@event_types) && @event_types.presence) ||
|
30
|
-
superclass.try(:event_types) if list.blank?
|
30
|
+
superclass.try(:event_types) || [] if list.blank?
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
@event_types =
|
35
|
-
expose_events! if expose
|
32
|
+
new_list = append ? event_types : []
|
33
|
+
new_list += list.flatten.compact.map(&:to_sym)
|
34
|
+
@event_types = new_list.uniq.freeze
|
35
|
+
expose_events!(*list) if expose
|
36
36
|
@event_types
|
37
37
|
end
|
38
38
|
|
39
39
|
protected
|
40
40
|
|
41
41
|
# Auxiliar method that creates easy-accessible callback assignment
|
42
|
-
def expose_events!
|
43
|
-
|
42
|
+
def expose_events!(*list)
|
43
|
+
list.each do |event_name|
|
44
44
|
next if method_defined?(event_name)
|
45
45
|
define_method(event_name) do |*args, **xargs, &block|
|
46
46
|
on(event_name, *args, **xargs, &block)
|
@@ -51,7 +51,7 @@ module Rails # :nodoc:
|
|
51
51
|
|
52
52
|
# Mostly for correct inheritance on instances
|
53
53
|
def all_events
|
54
|
-
current = (@events
|
54
|
+
current = defined?(@events) ? @events : {}
|
55
55
|
return current unless defined? super
|
56
56
|
Helpers.merge_hash_array(current, super)
|
57
57
|
end
|
@@ -6,9 +6,16 @@ module Rails # :nodoc:
|
|
6
6
|
# Helper module that allows other objects to hold an +assigned_to+ object
|
7
7
|
module WithOwner
|
8
8
|
def self.included(other)
|
9
|
+
other.extend(WithOwner::ClassMethods)
|
9
10
|
other.class_attribute(:owner, instance_writer: false)
|
10
11
|
end
|
11
12
|
|
13
|
+
module ClassMethods # :nodoc: all
|
14
|
+
def method_defined?(method_name)
|
15
|
+
super || owner&.method_defined?(method_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
12
19
|
private
|
13
20
|
|
14
21
|
def respond_to_missing?(*args) # :nodoc:
|
@@ -24,8 +31,9 @@ module Rails # :nodoc:
|
|
24
31
|
|
25
32
|
# Since owners are classes, this checks for the instance methods of
|
26
33
|
# it, since this is a instance method
|
27
|
-
def owner_respond_to?(method_name,
|
28
|
-
|
34
|
+
def owner_respond_to?(method_name, with_private = false)
|
35
|
+
return true if !owner.is_a?(Class) && owner.respond_to?(method_name, with_private)
|
36
|
+
(with_private ? %i[public protected private] : %i[public]).any? do |type|
|
29
37
|
owner.send("#{type}_instance_methods").include?(method_name)
|
30
38
|
end unless owner.nil?
|
31
39
|
end
|
@@ -189,7 +189,11 @@ module Rails # :nodoc:
|
|
189
189
|
SCHEMA_FIELD_TYPES.each do |kind, type_name|
|
190
190
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
191
191
|
def #{kind}_field?(name)
|
192
|
-
|
192
|
+
has_field?(:#{kind}, name)
|
193
|
+
end
|
194
|
+
|
195
|
+
def #{kind}_field(name)
|
196
|
+
find_field(:#{kind}, name)
|
193
197
|
end
|
194
198
|
|
195
199
|
def #{kind}_fields(&block)
|
@@ -77,7 +77,7 @@ module Rails # :nodoc:
|
|
77
77
|
# Get the GraphQL variables for a request
|
78
78
|
def gql_variables(variables = params[:variables])
|
79
79
|
case variables
|
80
|
-
when ::ActionController::Parameters then variables.permit
|
80
|
+
when ::ActionController::Parameters then variables.permit!.to_h
|
81
81
|
when String then variables.present? ? JSON.parse(variables) : {}
|
82
82
|
when Hash then variables
|
83
83
|
else {}
|
@@ -20,6 +20,7 @@ module Rails # :nodoc:
|
|
20
20
|
|
21
21
|
eager_autoload do
|
22
22
|
autoload_under :steps do
|
23
|
+
autoload :Authorizable
|
23
24
|
autoload :Organizable
|
24
25
|
autoload :Prepareable
|
25
26
|
autoload :Resolveable
|
@@ -42,8 +43,6 @@ module Rails # :nodoc:
|
|
42
43
|
attr_reader :schema, :visitor, :operations, :fragments, :errors,
|
43
44
|
:args, :response, :strategy, :stack
|
44
45
|
|
45
|
-
delegate :all_listeners, to: :schema
|
46
|
-
|
47
46
|
class << self
|
48
47
|
# Shortcut for initialize, set context, and execute
|
49
48
|
def execute(*args, schema: nil, namespace: :base, context: {}, **xargs)
|
@@ -79,6 +78,11 @@ module Rails # :nodoc:
|
|
79
78
|
ensure_schema!
|
80
79
|
end
|
81
80
|
|
81
|
+
# Cache all the schema listeners for this current request
|
82
|
+
def all_listeners
|
83
|
+
@all_listeners ||= schema.all_listeners
|
84
|
+
end
|
85
|
+
|
82
86
|
# Cache all the schema events for this current request
|
83
87
|
def all_events
|
84
88
|
@all_events ||= schema.all_events
|
@@ -127,7 +131,7 @@ module Rails # :nodoc:
|
|
127
131
|
# Add the given +exception+ to the errors using the +node+ location
|
128
132
|
def exception_to_error(exception, node, **xargs)
|
129
133
|
xargs[:exception] = exception.class.name
|
130
|
-
report_node_error(exception.message, node, **xargs)
|
134
|
+
report_node_error(xargs.delete(:message) || exception.message, node, **xargs)
|
131
135
|
end
|
132
136
|
|
133
137
|
# A little helper to report an error on a given node
|
@@ -8,6 +8,7 @@ module Rails # :nodoc:
|
|
8
8
|
# This class holds information about a given field that should be
|
9
9
|
# collected from the source of where it was requested.
|
10
10
|
class Component::Field < Component
|
11
|
+
include Authorizable
|
11
12
|
include ValueWriters
|
12
13
|
include SelectionSet
|
13
14
|
include Directives
|
@@ -40,13 +41,16 @@ module Rails # :nodoc:
|
|
40
41
|
# Override that considers the requested field directives and also the
|
41
42
|
# definition field events, both from itself and its directives events
|
42
43
|
def all_listeners
|
43
|
-
field.all_listeners + super
|
44
|
+
(request.cache(:listeners)[field] ||= field.all_listeners) + super
|
44
45
|
end
|
45
46
|
|
46
47
|
# Override that considers the requested field directives and also the
|
47
48
|
# definition field events, both from itself and its directives events
|
48
49
|
def all_events
|
49
|
-
@all_events ||= Helpers.merge_hash_array(
|
50
|
+
@all_events ||= Helpers.merge_hash_array(
|
51
|
+
(request.cache(:events)[field] ||= field.all_events),
|
52
|
+
super,
|
53
|
+
)
|
50
54
|
end
|
51
55
|
|
52
56
|
# Get and cache all the arguments for the field
|
@@ -129,6 +133,7 @@ module Rails # :nodoc:
|
|
129
133
|
def organize_then(&block)
|
130
134
|
super(block) do
|
131
135
|
check_assignment!
|
136
|
+
check_authorization!
|
132
137
|
|
133
138
|
parse_arguments
|
134
139
|
parse_directives
|
@@ -183,10 +188,10 @@ module Rails # :nodoc:
|
|
183
188
|
def trigger_event(event_name, **xargs)
|
184
189
|
return super if !defined?(@current_object) || @current_object.nil?
|
185
190
|
|
186
|
-
listeners = request.cache(:
|
191
|
+
listeners = request.cache(:listeners)[field] ||= field.all_listeners
|
187
192
|
return super unless listeners.include?(event_name)
|
188
193
|
|
189
|
-
callbacks = request.cache(:
|
194
|
+
callbacks = request.cache(:events)[field] ||= field.all_events
|
190
195
|
old_events, @all_events = @all_events, callbacks
|
191
196
|
super
|
192
197
|
ensure
|
@@ -44,9 +44,9 @@ module Rails # :nodoc:
|
|
44
44
|
|
45
45
|
item['path'] = path if path.present? && path.is_a?(Array)
|
46
46
|
item['extensions'] = extra.deep_stringify_keys if extra.present?
|
47
|
-
item['locations']
|
47
|
+
item['locations']&.map!(&:stringify_keys)
|
48
48
|
|
49
|
-
@items << item
|
49
|
+
@items << item.compact
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -70,13 +70,13 @@ module Rails # :nodoc:
|
|
70
70
|
args_source.try(:[], name.to_sym)
|
71
71
|
end
|
72
72
|
|
73
|
+
alias arg argument
|
74
|
+
|
73
75
|
# A combined helper for +instance_for+ and +set_on+
|
74
|
-
def on_instance(
|
75
|
-
set_on(
|
76
|
+
def on_instance(object, &block)
|
77
|
+
set_on(object.is_a?(Class) ? instance_for(object) : object, &block)
|
76
78
|
end
|
77
79
|
|
78
|
-
alias arg argument
|
79
|
-
|
80
80
|
protected
|
81
81
|
|
82
82
|
# When performing an event under a field object, the keyed-based
|
@@ -6,12 +6,13 @@ module Rails # :nodoc:
|
|
6
6
|
# Helper module to collect the directives from fragments, operations, and
|
7
7
|
# fields.
|
8
8
|
module Directives
|
9
|
-
# Get the list of listeners from
|
9
|
+
# Get the list of listeners from directives set during the request only
|
10
10
|
def all_listeners
|
11
11
|
directives.map(&:all_listeners).reduce(:+) || Set.new
|
12
12
|
end
|
13
13
|
|
14
|
-
# Get the list of events from
|
14
|
+
# Get the list of events from directives set during the request only and
|
15
|
+
# then caches it by request
|
15
16
|
def all_events
|
16
17
|
@all_events ||= directives.map(&:all_events).inject({}) do |lhash, rhash|
|
17
18
|
Helpers.merge_hash_array(lhash, rhash)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails # :nodoc:
|
4
|
+
module GraphQL # :nodoc:
|
5
|
+
class Request # :nodoc:
|
6
|
+
# Helper methods for the authorize step of a request
|
7
|
+
module Authorizable
|
8
|
+
# Event used to perform an authorization step
|
9
|
+
class Event < GraphQL::Event
|
10
|
+
# Similar to trigger for object, but with an extra extension for
|
11
|
+
# instance methods defined on the given object
|
12
|
+
def authorize_using(object, send_args, events = nil)
|
13
|
+
cache = data[:request].cache(name)[object] ||= []
|
14
|
+
return false if cache.present? && cache.none?
|
15
|
+
args, xargs = send_args
|
16
|
+
|
17
|
+
# Authorize through instance method
|
18
|
+
using_object = cache[0] ||= authorize_on_object(object)
|
19
|
+
set_on(using_object) do |instance|
|
20
|
+
instance.public_send("#{name}!", self, *args, **xargs)
|
21
|
+
end if using_object
|
22
|
+
|
23
|
+
# Authorize through events
|
24
|
+
using_events = cache[1] ||= (events || object.all_events[name]).presence
|
25
|
+
using_events&.each { |block| block.call(self, *args, **xargs) }
|
26
|
+
|
27
|
+
# Does any authorize process ran
|
28
|
+
cache.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Simply unauthorize the operation
|
32
|
+
def unauthorize!(*, message: nil, **)
|
33
|
+
raise UnauthorizedFieldError, message || <<~MSG.squish
|
34
|
+
Unauthorized access to "#{field.gql_name}" field.
|
35
|
+
MSG
|
36
|
+
end
|
37
|
+
|
38
|
+
# Simply authorize the operation
|
39
|
+
def authorize!(*)
|
40
|
+
throw :authorized
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Check if it should run call an +authorize!+ method on the given
|
46
|
+
# +object+. Classes are turn into instance through strategy
|
47
|
+
def authorize_on_object(object)
|
48
|
+
as_class = object.is_a?(Class)
|
49
|
+
checker = as_class ? :method_defined? : :respond_to?
|
50
|
+
|
51
|
+
return false unless object.public_send(checker, :authorize!)
|
52
|
+
as_class ? data[:request].strategy.instance_for(object) : object
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check if the field is correctly authorized to be executed
|
57
|
+
def check_authorization!
|
58
|
+
return unless field.authorizable?
|
59
|
+
*args, block = field.authorizer
|
60
|
+
|
61
|
+
catch(:authorized) do
|
62
|
+
event = authorization_event
|
63
|
+
schema_events = request.all_events[:authorize]
|
64
|
+
executed = event.authorize_using(schema, args, schema_events)
|
65
|
+
|
66
|
+
element = field
|
67
|
+
while element && element != schema
|
68
|
+
executed = event.authorize_using(element, args) || executed
|
69
|
+
element = element.try(:owner)
|
70
|
+
end
|
71
|
+
|
72
|
+
if block.present?
|
73
|
+
block.call(event, *args[0], **args[1])
|
74
|
+
executed = true
|
75
|
+
end
|
76
|
+
|
77
|
+
event.unauthorize!(message: <<~MSG.squish) unless executed
|
78
|
+
Authorization required but unable to be executed
|
79
|
+
MSG
|
80
|
+
end
|
81
|
+
rescue UnauthorizedFieldError => error
|
82
|
+
request.rescue_with_handler(error)
|
83
|
+
request.exception_to_error(error, @node)
|
84
|
+
invalidate!
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Build and store the authorization event
|
90
|
+
def authorization_event
|
91
|
+
Event.new(:authorize, self,
|
92
|
+
request: request,
|
93
|
+
schema: schema,
|
94
|
+
field: field,
|
95
|
+
memo: memo,
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -37,6 +37,7 @@ module Rails # :nodoc:
|
|
37
37
|
|
38
38
|
def initialize(request)
|
39
39
|
@request = request
|
40
|
+
@objects_pool = {}
|
40
41
|
collect_request_listeners
|
41
42
|
end
|
42
43
|
|
@@ -205,7 +206,6 @@ module Rails # :nodoc:
|
|
205
206
|
# it can load data in a pretty smart way
|
206
207
|
def collect_data
|
207
208
|
@data_pool = {}
|
208
|
-
@objects_pool = {}
|
209
209
|
@context = request.build(Request::Context)
|
210
210
|
|
211
211
|
# TODO: Create an orchestrator to allow cross query loading
|
data/lib/rails/graphql/source.rb
CHANGED
@@ -145,6 +145,11 @@ module Rails # :nodoc:
|
|
145
145
|
defined?(@built) && !!@built
|
146
146
|
end
|
147
147
|
|
148
|
+
# Checks if a given method can act as resolver
|
149
|
+
def gql_resolver?(method_name)
|
150
|
+
(instance_methods - GraphQL::Source.instance_methods).include?(method_name)
|
151
|
+
end
|
152
|
+
|
148
153
|
# Attach all defined schema fields into the schemas using the namespaces
|
149
154
|
# configured for the source
|
150
155
|
def attach_fields!
|
@@ -91,7 +91,7 @@ module Rails
|
|
91
91
|
def build_reflection_fields(holder)
|
92
92
|
each_reflection(holder) do |item|
|
93
93
|
next if holder.field?(item.name)
|
94
|
-
type_map_after_register(item.klass
|
94
|
+
type_map_after_register(item.klass) do |type|
|
95
95
|
next unless (type.object? && type.try(:assigned_to) != item.klass) ||
|
96
96
|
type.interface?
|
97
97
|
|
@@ -100,7 +100,7 @@ module Rails # :nodoc:
|
|
100
100
|
next if model.base_class == model
|
101
101
|
|
102
102
|
# TODO: Allow nested inheritance for setting up implementation
|
103
|
-
type_map_after_register(model.base_class
|
103
|
+
type_map_after_register(model.base_class) do |type|
|
104
104
|
object.implements(type) if type.interface?
|
105
105
|
end
|
106
106
|
end
|
@@ -124,6 +124,11 @@ module Rails # :nodoc:
|
|
124
124
|
@enums ||= model.defined_enums.dup
|
125
125
|
end
|
126
126
|
|
127
|
+
# Just a little override to ensure that both model and table are ready
|
128
|
+
def build!
|
129
|
+
super if model&.table_exists?
|
130
|
+
end
|
131
|
+
|
127
132
|
protected
|
128
133
|
|
129
134
|
# Check if a given +attr_name+ is associated with a presence validator
|
@@ -145,13 +150,13 @@ module Rails # :nodoc:
|
|
145
150
|
end
|
146
151
|
|
147
152
|
# Prepare to load multiple records from the underlying table
|
148
|
-
def load_records
|
149
|
-
inject_scopes(
|
153
|
+
def load_records(scope = model.default_scoped)
|
154
|
+
inject_scopes(scope, :relation)
|
150
155
|
end
|
151
156
|
|
152
157
|
# Prepare to load a single record from the underlying table
|
153
|
-
def load_record
|
154
|
-
|
158
|
+
def load_record(scope = model.default_scoped)
|
159
|
+
scope.find(event.argument(primary_key))
|
155
160
|
end
|
156
161
|
|
157
162
|
# Get the chain result and preload the records with thre resulting scope
|
@@ -161,7 +166,7 @@ module Rails # :nodoc:
|
|
161
166
|
|
162
167
|
# Collect a scope for filters applied to a given association
|
163
168
|
def build_association_scope(association)
|
164
|
-
scope = model._reflect_on_association(association).klass.
|
169
|
+
scope = model._reflect_on_association(association).klass.default_scoped
|
165
170
|
|
166
171
|
# Apply proxied injected scopes
|
167
172
|
proxied = event.field.try(:proxied_owner)
|
@@ -45,7 +45,7 @@ module Rails # :nodoc:
|
|
45
45
|
# Add a new scoped param to the list
|
46
46
|
def scoped_argument(param, type = :string, proc_method = nil, **settings, &block)
|
47
47
|
block = proc_method if proc_method.present? && block.nil?
|
48
|
-
argument = Argument.new(param, type, **settings
|
48
|
+
argument = Argument.new(param, type, **settings, owner: self, &block)
|
49
49
|
(@scoped_arguments ||= {})[argument.name] = argument
|
50
50
|
end
|
51
51
|
|
@@ -223,11 +223,11 @@ module Rails # :nodoc:
|
|
223
223
|
namespaces += [:base] unless namespaces.include?(:base) || exclusive
|
224
224
|
|
225
225
|
iterated = []
|
226
|
-
enumerator = Enumerator::Lazy.new(namespaces.uniq) do |yielder,
|
227
|
-
next unless @index.key?(
|
226
|
+
enumerator = Enumerator::Lazy.new(namespaces.uniq) do |yielder, item|
|
227
|
+
next unless @index.key?(item)
|
228
228
|
|
229
229
|
# Only iterate over string based types
|
230
|
-
@index[
|
230
|
+
@index[item][base_class]&.each do |_key, value|
|
231
231
|
next if iterated.include?(value = value.call) || value.blank?
|
232
232
|
iterated << value
|
233
233
|
yielder << value
|
@@ -252,7 +252,7 @@ module Rails # :nodoc:
|
|
252
252
|
position = callbacks[name_or_key].size
|
253
253
|
|
254
254
|
callbacks[name_or_key] << ->(n, b, result) do
|
255
|
-
return unless b === base_class && namespaces.include?(n)
|
255
|
+
return unless b === base_class && (n === :base || namespaces.include?(n))
|
256
256
|
block.call(result)
|
257
257
|
position
|
258
258
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'integration/config'
|
2
|
+
|
3
|
+
class Integration_Memory_AuthorizationTest < GraphQL::IntegrationTestCase
|
4
|
+
load_schema 'authorization'
|
5
|
+
|
6
|
+
SCHEMA = ::AuthorizationSchema
|
7
|
+
EXCEPTION_NAME = 'Rails::GraphQL::UnauthorizedFieldError'
|
8
|
+
EXCEPTION_PATH = ['errors', 0, 'extensions', 'exception']
|
9
|
+
|
10
|
+
SAMPLE1 = { data: { sample1: 'Ok 1' } }
|
11
|
+
SAMPLE2 = { data: { sample2: 'Ok 2' } }
|
12
|
+
|
13
|
+
def test_field_event_types
|
14
|
+
assert_includes(SCHEMA.query_field(:sample1).event_types, :authorize)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_without_authorization
|
18
|
+
assert_result(SAMPLE1, '{ sample1 }')
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_with_simple_authorization
|
22
|
+
assert_exception('{ sample2 }')
|
23
|
+
assert_result({ sample1: 'Ok 1', sample2: nil }, '{ sample1 sample2 }', dig: 'data')
|
24
|
+
|
25
|
+
SCHEMA.stub_imethod(:authorize!, &:authorize!).call do
|
26
|
+
assert_result(SAMPLE2, '{ sample2 }')
|
27
|
+
end
|
28
|
+
|
29
|
+
SCHEMA.stub_imethod(:authorize!, &:unauthorize!).call do
|
30
|
+
assert_exception('{ sample2 }')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_with_block_authorization
|
35
|
+
field = SCHEMA.query_field(:sample1)
|
36
|
+
|
37
|
+
field.stub_ivar(:@authorizer, [[], {}, method(:executed!)]) do
|
38
|
+
assert_executed { assert_result(SAMPLE1, '{ sample1 }') }
|
39
|
+
end
|
40
|
+
|
41
|
+
block = ->(ev) { executed! && ev.unauthorize! }
|
42
|
+
field.stub_ivar(:@authorizer, [[], {}, block]) do
|
43
|
+
assert_executed { assert_exception('{ sample1 }') }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_with_field_event_authorization
|
48
|
+
field = SCHEMA.query_field(:sample2)
|
49
|
+
|
50
|
+
field.stub_ivar(:@events, { authorize: [method(:executed!)] }) do
|
51
|
+
assert_executed { assert_result(SAMPLE2, '{ sample2 }') }
|
52
|
+
|
53
|
+
assert_executed do
|
54
|
+
field.on(:authorize) { |event| event.unauthorize! }
|
55
|
+
assert_exception('{ sample2 }')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_with_directive
|
61
|
+
field = SCHEMA.query_field(:sample2)
|
62
|
+
|
63
|
+
auth_directive = unmapped_class(Rails::GraphQL::Directive)
|
64
|
+
auth_directive.on(:authorize, &method(:executed!))
|
65
|
+
|
66
|
+
auth_directive.stub_ivar(:@gql_name, 'AuthDirective') do
|
67
|
+
field.stub_ivar(:@directives, [auth_directive.new]) do
|
68
|
+
assert_executed { assert_result(SAMPLE2, '{ sample2 }') }
|
69
|
+
end
|
70
|
+
|
71
|
+
SCHEMA.stub_ivar(:@directives, [auth_directive.new]) do
|
72
|
+
assert_executed { assert_result(SAMPLE2, '{ sample2 }') }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_authorize_bypass
|
78
|
+
field = SCHEMA.query_field(:sample2)
|
79
|
+
auth_block = ->(e, event) { e.call && event.authorize! }.curry(2)[method(:executed!)]
|
80
|
+
unauth_block = ->(e, event) { e.call && event.unauthorize! }.curry(2)[method(:executed!)]
|
81
|
+
|
82
|
+
field.stub_ivar(:@events, { authorize: [unauth_block] }) do
|
83
|
+
SCHEMA.stub_imethod(:authorize!, &auth_block).call do
|
84
|
+
assert_executed { assert_result(SAMPLE2, '{ sample2 }') }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
field.stub_ivar(:@events, { authorize: [auth_block] }) do
|
89
|
+
SCHEMA.stub_imethod(:authorize!, &unauth_block).call do
|
90
|
+
assert_executed { assert_exception('{ sample2 }') }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def executed!(*)
|
98
|
+
@executed += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def assert_executed(times = 1)
|
102
|
+
@executed = 0
|
103
|
+
yield
|
104
|
+
assert_equal(times, @executed)
|
105
|
+
ensure
|
106
|
+
remove_instance_variable(:@executed)
|
107
|
+
end
|
108
|
+
|
109
|
+
def assert_exception(query, *args, **xargs)
|
110
|
+
assert_result(EXCEPTION_NAME, query, *args, dig: EXCEPTION_PATH, **xargs)
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class AuthorizationSchema < GraphQL::Schema
|
2
|
+
namespace :authorization
|
3
|
+
|
4
|
+
configure do |config|
|
5
|
+
config.enable_string_collector = false
|
6
|
+
end
|
7
|
+
|
8
|
+
query_fields do
|
9
|
+
field(:sample1, :string).resolve { 'Ok 1' }
|
10
|
+
field(:sample2, :string).authorize.resolve { 'Ok 2' }
|
11
|
+
end
|
12
|
+
end
|
data/test/test_ext.rb
CHANGED
@@ -28,6 +28,20 @@ class Object < BasicObject
|
|
28
28
|
const_set(name, old_value) if defined? old_value
|
29
29
|
end
|
30
30
|
|
31
|
+
def stub_imethod(name, &block)
|
32
|
+
lambda do |&run_block|
|
33
|
+
alias_method(:"_old_#{name}", name) if (reset_old = method_defined?(name))
|
34
|
+
define_method(name, &block)
|
35
|
+
run_block.call
|
36
|
+
ensure
|
37
|
+
undef_method(name)
|
38
|
+
if reset_old
|
39
|
+
alias_method(name, :"_old_#{name}")
|
40
|
+
undef_method(:"_old_#{name}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
31
45
|
def get_reset_ivar(name, *extra, &block)
|
32
46
|
instance_variable_set(name, extra.first) if extra.any?
|
33
47
|
instance_exec(&block)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Silva
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -307,6 +307,7 @@ files:
|
|
307
307
|
- lib/rails/graphql/errors.rb
|
308
308
|
- lib/rails/graphql/event.rb
|
309
309
|
- lib/rails/graphql/field.rb
|
310
|
+
- lib/rails/graphql/field/authorized_field.rb
|
310
311
|
- lib/rails/graphql/field/input_field.rb
|
311
312
|
- lib/rails/graphql/field/mutation_field.rb
|
312
313
|
- lib/rails/graphql/field/output_field.rb
|
@@ -355,6 +356,7 @@ files:
|
|
355
356
|
- lib/rails/graphql/request/helpers/directives.rb
|
356
357
|
- lib/rails/graphql/request/helpers/selection_set.rb
|
357
358
|
- lib/rails/graphql/request/helpers/value_writers.rb
|
359
|
+
- lib/rails/graphql/request/steps/authorizable.rb
|
358
360
|
- lib/rails/graphql/request/steps/organizable.rb
|
359
361
|
- lib/rails/graphql/request/steps/prepareable.rb
|
360
362
|
- lib/rails/graphql/request/steps/resolveable.rb
|
@@ -427,10 +429,12 @@ files:
|
|
427
429
|
- test/graphql/type_map_test.rb
|
428
430
|
- test/graphql/type_test.rb
|
429
431
|
- test/graphql_test.rb
|
432
|
+
- test/integration/authorization/authorization_test.rb
|
430
433
|
- test/integration/config.rb
|
431
434
|
- test/integration/memory/star_wars_introspection_test.rb
|
432
435
|
- test/integration/memory/star_wars_query_test.rb
|
433
436
|
- test/integration/memory/star_wars_validation_test.rb
|
437
|
+
- test/integration/schemas/authorization.rb
|
434
438
|
- test/integration/schemas/memory.rb
|
435
439
|
- test/integration/schemas/sqlite.rb
|
436
440
|
- test/integration/sqlite/star_wars_introspection_test.rb
|
@@ -503,7 +507,9 @@ test_files:
|
|
503
507
|
- test/integration/memory/star_wars_validation_test.rb
|
504
508
|
- test/integration/schemas/memory.rb
|
505
509
|
- test/integration/schemas/sqlite.rb
|
510
|
+
- test/integration/schemas/authorization.rb
|
506
511
|
- test/integration/sqlite/star_wars_introspection_test.rb
|
507
512
|
- test/integration/sqlite/star_wars_mutation_test.rb
|
508
513
|
- test/integration/sqlite/star_wars_query_test.rb
|
514
|
+
- test/integration/authorization/authorization_test.rb
|
509
515
|
- test/test_ext.rb
|