From 93ab78819676c4785399ebdd75e8842d21007f44 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Tue, 3 Dec 2024 07:12:19 -0500 Subject: [PATCH] Add possible_types check for unions that are used only for loads: --- lib/graphql/schema/member/has_arguments.rb | 18 +++++++++++++----- lib/graphql/schema/visibility/migration.rb | 1 + lib/graphql/schema/visibility/profile.rb | 6 ++++++ lib/graphql/schema/warden.rb | 15 ++++++++++++++- spec/graphql/schema/union_spec.rb | 3 ++- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/graphql/schema/member/has_arguments.rb b/lib/graphql/schema/member/has_arguments.rb index 77c164d534..323850b997 100644 --- a/lib/graphql/schema/member/has_arguments.rb +++ b/lib/graphql/schema/member/has_arguments.rb @@ -359,7 +359,8 @@ def authorize_application_object(argument, id, context, loaded_application_objec if application_object.nil? nil else - maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context) + arg_loads_type = argument.loads + maybe_lazy_resolve_type = context.schema.resolve_type(arg_loads_type, application_object, context) context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result| if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 application_object_type, application_object = resolve_type_result @@ -368,10 +369,17 @@ def authorize_application_object(argument, id, context, loaded_application_objec # application_object is already assigned end - if !( - context.types.possible_types(argument.loads).include?(application_object_type) || - context.types.loadable?(argument.loads, context) - ) + passes_possible_types_check = if context.types.loadable?(arg_loads_type, context) + if arg_loads_type.kind.union? + # This union is used in `loads:` but not otherwise visible to this query + context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type) + else + true + end + else + context.types.possible_types(arg_loads_type).include?(application_object_type) + end + if !passes_possible_types_check err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object) application_object = load_application_object_failed(err) end diff --git a/lib/graphql/schema/visibility/migration.rb b/lib/graphql/schema/visibility/migration.rb index 01f65009b8..8639fa141a 100644 --- a/lib/graphql/schema/visibility/migration.rb +++ b/lib/graphql/schema/visibility/migration.rb @@ -112,6 +112,7 @@ def loaded_types :all_types_h, :fields, :loadable?, + :loadable_possible_types, :type, :arguments, :argument, diff --git a/lib/graphql/schema/visibility/profile.rb b/lib/graphql/schema/visibility/profile.rb index 1e6d2ae278..db609391c1 100644 --- a/lib/graphql/schema/visibility/profile.rb +++ b/lib/graphql/schema/visibility/profile.rb @@ -84,6 +84,8 @@ def initialize(name: nil, context:, schema:) @cached_arguments = Hash.new do |h, owner| h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments) end.compare_by_identity + + @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity end def field_on_visible_interface?(field, owner) @@ -249,6 +251,10 @@ def loadable?(t, _ctx) !@all_types[t.graphql_name] && @cached_visible[t] end + def loadable_possible_types(t, _ctx) + @loadable_possible_types[t] + end + def loaded_types @all_types.values end diff --git a/lib/graphql/schema/warden.rb b/lib/graphql/schema/warden.rb index d1241eb017..16be514978 100644 --- a/lib/graphql/schema/warden.rb +++ b/lib/graphql/schema/warden.rb @@ -72,6 +72,7 @@ def visible_type_membership?(tm, ctx); tm.visible?(ctx); end def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end def arguments(owner, ctx); owner.arguments(ctx); end def loadable?(type, ctx); type.visible?(ctx); end + def loadable_possible_types(type, ctx); type.possible_types(ctx); end def visibility_profile @visibility_profile ||= Warden::VisibilityProfile.new(self) end @@ -106,6 +107,7 @@ def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable De def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end def reachable_type?(type_name); true; end def loadable?(type, _ctx); true; end + def loadable_possible_types(union_type, _ctx); union_type.possible_types; end def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end def interfaces(obj_type); obj_type.interfaces; end @@ -180,6 +182,10 @@ def loadable?(t, ctx) # TODO remove ctx here? @warden.loadable?(t, ctx) end + def loadable_possible_types(t, ctx) + @warden.loadable_possible_types(t, ctx) + end + def reachable_type?(type_name) !!@warden.reachable_type?(type_name) end @@ -204,7 +210,7 @@ def initialize(context:, schema:) @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays = @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships = @visible_and_reachable_type = @unions = @unfiltered_interfaces = - @reachable_type_set = @visibility_profile = + @reachable_type_set = @visibility_profile = @loadable_possible_types = nil @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden } end @@ -229,6 +235,13 @@ def loadable?(type, _ctx) !reachable_type_set.include?(type) && visible_type?(type) end + def loadable_possible_types(union_type, _ctx) + @loadable_possible_types ||= read_through do |t| + t.possible_types # unfiltered + end + @loadable_possible_types[union_type] + end + # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) def get_type(type_name) @visible_types ||= read_through do |name| diff --git a/spec/graphql/schema/union_spec.rb b/spec/graphql/schema/union_spec.rb index 4608105b8a..0f019799f6 100644 --- a/spec/graphql/schema/union_spec.rb +++ b/spec/graphql/schema/union_spec.rb @@ -456,7 +456,8 @@ def self.resolve_type(abs_type, obj, ctx) assert_equal "Video", res["data"]["mediaItemType"] res = UnionLoadsSchema.execute(query_str, variables: { mediaId: "Post/Year in Review" }) - assert_nil res["data"] + assert_nil res["data"]["mediaItemType"] + assert_equal ["No object found for `id: \"Post/Year in Review\"`"], res["errors"].map { |e| e["message"] } end end end