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

new(filter, context:, schema:) click to toggle source

@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 40
def initialize(filter, context,, schema))
  @schema = schema
  @visibility_cache = read_through { |m| filter.call(m, context) }
end

Public Instance Methods

arguments(argument_owner) click to toggle source

@param argument_owner [GraphQL::Field, GraphQL::InputObjectType] @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`

# File lib/graphql/schema/warden.rb, line 96
def arguments(argument_owner)
  @visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_field?(a) } }
  @visible_arguments[argument_owner]
end
directives() click to toggle source
# File lib/graphql/schema/warden.rb, line 113
def directives
  @schema.directives.each_value.select { |d| visible?(d) }
end
enum_values(enum_defn) click to toggle source

@return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`

# File lib/graphql/schema/warden.rb, line 102
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
fields(type_defn) click to toggle source

@param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] @return [Array<GraphQL::Field>] Fields on `type_defn`

# File lib/graphql/schema/warden.rb, line 89
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
get_field(parent_type, field_name) click to toggle source

@return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists

# File lib/graphql/schema/warden.rb, line 65
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
get_type(type_name) click to toggle source

@return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)

# File lib/graphql/schema/warden.rb, line 51
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
interfaces(obj_type) click to toggle source

@return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`

# File lib/graphql/schema/warden.rb, line 108
def interfaces(obj_type)
  @visible_interfaces ||= read_through { |t| t.interfaces.select { |i| visible?(i) } }
  @visible_interfaces[obj_type]
end
possible_types(type_defn) click to toggle source

@return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`

# File lib/graphql/schema/warden.rb, line 82
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
root_type_for_operation(op_name) click to toggle source
# File lib/graphql/schema/warden.rb, line 117
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
types() click to toggle source

@return [Array<GraphQL::BaseType>] Visible types in the schema

# File lib/graphql/schema/warden.rb, line 46
def types
  @types ||= @schema.types.each_value.select { |t| visible_type?(t) }
end

Private Instance Methods

orphan_type?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 160
def orphan_type?(type_defn)
  @schema.orphan_types.include?(type_defn)
end
read_through() { |k| ... } click to toggle source
# File lib/graphql/schema/warden.rb, line 179
def read_through
  Hash.new { |h, k| h[k] = yield(k) }
end
referenced?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 155
def referenced?(type_defn)
  members = @schema.references_to(type_defn.unwrap.name)
  members.any? { |m| visible?(m) }
end
root_type?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 151
def root_type?(type_defn)
  @schema.root_types.include?(type_defn)
end
union_memberships(obj_type) click to toggle source
# File lib/graphql/schema/warden.rb, line 128
def union_memberships(obj_type)
  @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
  @unions[obj_type]
end
visible?(member) click to toggle source
# File lib/graphql/schema/warden.rb, line 175
def visible?(member)
  @visibility_cache[member]
end
visible_abstract_type?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 164
def visible_abstract_type?(type_defn)
  type_defn.kind.object? && (
      interfaces(type_defn).any? ||
      union_memberships(type_defn).any?
    )
end
visible_field?(field_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 133
def visible_field?(field_defn)
  visible?(field_defn) && visible_type?(field_defn.type.unwrap)
end
visible_possible_types?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 171
def visible_possible_types?(type_defn)
  @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
end
visible_type?(type_defn) click to toggle source
# File lib/graphql/schema/warden.rb, line 137
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