class GraphQL::Schema::Warden
Restrict access to a {GraphQL::Schema} with a user-defined filter.
When validating and executing a query, all access to schema members should go through a warden. If you access the schema directly, you may show a client something that it shouldn't be allowed to see.
@example Hidding private fields
private_members = -> (member, ctx) { member.metadata[:private] } result = Schema.execute(query_string, except: private_members)
@example Custom filter implementation
# It must respond to `#call(member)`. class MissingRequiredFlags def initialize(user) @user = user end # Return `false` if any required flags are missing def call(member, ctx) member.metadata[:required_flags].any? do |flag| !@user.has_flag?(flag) end end end # Then, use the custom filter in query: missing_required_flags = MissingRequiredFlags.new(current_user) # This query can only access members which match the user's flags result = Schema.execute(query_string, except: missing_required_flags)
@api private
Public Class Methods
@param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true @param context [GraphQL::Query::Context] @param schema [GraphQL::Schema] @param deep_check [Boolean]
# File lib/graphql/schema/warden.rb, line 41 def initialize(filter, context:, schema:) @schema = schema @visibility_cache = read_through { |m| filter.call(m, context) } end
Public Instance Methods
@param argument_owner [GraphQL::Field, GraphQL::InputObjectType] @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
# File lib/graphql/schema/warden.rb, line 97 def arguments(argument_owner) @visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_field?(a) } } @visible_arguments[argument_owner] end
# File lib/graphql/schema/warden.rb, line 114 def directives @schema.directives.each_value.select { |d| visible?(d) } end
@return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`
# File lib/graphql/schema/warden.rb, line 103 def enum_values(enum_defn) @visible_enum_values ||= read_through { |e| e.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) } } @visible_enum_values[enum_defn] end
@param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] @return [Array<GraphQL::Field>] Fields on `type_defn`
# File lib/graphql/schema/warden.rb, line 90 def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t).each_value.select { |f| visible_field?(f) } } @visible_fields[type_defn] end
@return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
# File lib/graphql/schema/warden.rb, line 66 def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| field_defn = @schema.get_field(type, f_name) if field_defn && visible_field?(field_defn) field_defn else nil end end end @visible_parent_fields[parent_type][field_name] end
@return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
# File lib/graphql/schema/warden.rb, line 52 def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.types.fetch(name, nil) if type_defn && visible_type?(type_defn) type_defn else nil end end @visible_types[type_name] end
@return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
# File lib/graphql/schema/warden.rb, line 109 def interfaces(obj_type) @visible_interfaces ||= read_through { |t| t.interfaces.select { |i| visible?(i) } } @visible_interfaces[obj_type] end
@return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
# File lib/graphql/schema/warden.rb, line 83 def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } } @visible_possible_types[type_defn] end
# File lib/graphql/schema/warden.rb, line 118 def root_type_for_operation(op_name) root_type = @schema.root_type_for_operation(op_name) if root_type && visible?(root_type) root_type else nil end end
@return [Array<GraphQL::BaseType>] Visible types in the schema
# File lib/graphql/schema/warden.rb, line 47 def types @types ||= @schema.types.each_value.select { |t| visible_type?(t) } end
Private Instance Methods
# File lib/graphql/schema/warden.rb, line 161 def orphan_type?(type_defn) @schema.orphan_types.include?(type_defn) end
# File lib/graphql/schema/warden.rb, line 180 def read_through Hash.new { |h, k| h[k] = yield(k) } end
# File lib/graphql/schema/warden.rb, line 156 def referenced?(type_defn) members = @schema.references_to(type_defn.unwrap.name) members.any? { |m| visible?(m) } end
# File lib/graphql/schema/warden.rb, line 152 def root_type?(type_defn) @schema.root_types.include?(type_defn) end
# File lib/graphql/schema/warden.rb, line 129 def union_memberships(obj_type) @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } } @unions[obj_type] end
# File lib/graphql/schema/warden.rb, line 176 def visible?(member) @visibility_cache[member] end
# File lib/graphql/schema/warden.rb, line 165 def visible_abstract_type?(type_defn) type_defn.kind.object? && ( interfaces(type_defn).any? || union_memberships(type_defn).any? ) end
# File lib/graphql/schema/warden.rb, line 134 def visible_field?(field_defn) visible?(field_defn) && visible_type?(field_defn.type.unwrap) end
# File lib/graphql/schema/warden.rb, line 172 def visible_possible_types?(type_defn) @schema.possible_types(type_defn).any? { |t| visible_type?(t) } end
# File lib/graphql/schema/warden.rb, line 138 def visible_type?(type_defn) return false unless visible?(type_defn) return true if root_type?(type_defn) return true if type_defn.introspection? if type_defn.kind.union? visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn)) elsif type_defn.kind.interface? visible_possible_types?(type_defn) else referenced?(type_defn) || visible_abstract_type?(type_defn) end end