Skip to content
Snippets Groups Projects
Commit 79c11a6d authored by Brett Walker's avatar Brett Walker
Browse files

Create the new AST RecursionAnalyzer

ported from the original RecursionAnalyzer
parent fdc9f1c3
No related branches found
No related tags found
No related merge requests found
......@@ -10,8 +10,6 @@ class LoggerAnalyzer < GraphQL::Analysis::AST::Analyzer
FIELD_USAGE_ANALYZER = GraphQL::Analysis::AST::FieldUsage
ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze
attr_reader :results
def initialize(query)
super
......@@ -48,6 +46,8 @@ def result
private
attr_reader :results
def process_variables(variables)
filtered_variables = filter_sensitive_variables(variables)
filtered_variables.try(:to_s) || filtered_variables
......
......@@ -10,50 +10,61 @@ class RecursionAnalyzer < GraphQL::Analysis::AST::Analyzer
IGNORED_FIELDS = %w(node edges nodes ofType).freeze
RECURSION_THRESHOLD = 2
def initial_value(query)
{
recurring_fields: {}
}
def initialize(query)
super
@node_visits = {}
@recurring_fields = {}
end
def call(memo, visit_type, irep_node)
return memo if skip_node?(irep_node)
def on_enter_field(node, _parent, visitor)
return if skip_node?(node, visitor)
node_name = irep_node.ast_node.name
times_encountered = memo[node_name] || 0
node_name = node.name
node_visits[node_name] ||= 0
node_visits[node_name] += 1
if visit_type == :enter
times_encountered += 1
memo[:recurring_fields][node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered)
else
times_encountered -= 1
end
times_encountered = @node_visits[node_name]
recurring_fields[node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered)
end
# Visitors are all defined on the AST::Analyzer base class
# We override them for custom analyzers.
def on_leave_field(node, _parent, visitor)
return if skip_node?(node, visitor)
memo[node_name] = times_encountered
memo
node_name = node.name
node_visits[node_name] ||= 0
node_visits[node_name] -= 1
end
def final_value(memo)
recurring_fields = memo[:recurring_fields]
recurring_fields = recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
if recurring_fields.any?
GraphQL::AnalysisError.new("Recursive query - too many of fields '#{recurring_fields}' detected in single branch of the query")
def result
@recurring_fields = @recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
if @recurring_fields.any?
GraphQL::AnalysisError.new("Recursive query - too many of fields '#{@recurring_fields}' detected in single branch of the query")
end
end
private
attr_reader :node_visits, :recurring_fields
def recursion_too_deep?(node_name, times_encountered)
return if IGNORED_FIELDS.include?(node_name)
times_encountered > recursion_threshold
end
def skip_node?(irep_node)
ast_node = irep_node.ast_node
!ast_node.is_a?(GraphQL::Language::Nodes::Field) || ast_node.selections.empty?
def skip_node?(node, visitor)
# We don't want to count skipped fields or fields
# inside fragment definitions
return false if visitor.skipping? || visitor.visiting_fragment_definition?
!node.is_a?(GraphQL::Language::Nodes::Field) || node.selections.empty?
end
# separated into a method for use in allow_high_graphql_recursion
def recursion_threshold
RECURSION_THRESHOLD
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::QueryAnalyzers::AST::RecursionAnalyzer do
let(:query) { GraphQL::Query.new(GitlabSchema, document: document, context: {}, variables: { body: 'some note' }) }
context 'when recursion threshold not exceeded' do
let(:document) do
GraphQL.parse <<-GRAPHQL
query recurse {
group(fullPath: "h5bp") {
projects {
nodes {
name
group {
projects {
nodes {
name
}
}
}
}
}
}
}
GRAPHQL
end
it 'returns the complexity, depth, duration, etc' do
result = GraphQL::Analysis::AST.analyze_query(query, [described_class], multiplex_analyzers: [])
expect(result.first).to be_nil
end
end
context 'when recursion threshold exceeded' do
let(:document) do
GraphQL.parse <<-GRAPHQL
query recurse {
group(fullPath: "h5bp") {
projects {
nodes {
name
group {
projects {
nodes {
name
group {
projects {
nodes {
name
}
}
}
}
}
}
}
}
}
}
GRAPHQL
end
it 'returns error' do
result = GraphQL::Analysis::AST.analyze_query(query, [described_class], multiplex_analyzers: [])
expect(result.first.is_a?(GraphQL::AnalysisError)).to be_truthy
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment