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 ...@@ -10,8 +10,6 @@ class LoggerAnalyzer < GraphQL::Analysis::AST::Analyzer
FIELD_USAGE_ANALYZER = GraphQL::Analysis::AST::FieldUsage FIELD_USAGE_ANALYZER = GraphQL::Analysis::AST::FieldUsage
ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze ALL_ANALYZERS = [COMPLEXITY_ANALYZER, DEPTH_ANALYZER, FIELD_USAGE_ANALYZER].freeze
attr_reader :results
def initialize(query) def initialize(query)
super super
...@@ -48,6 +46,8 @@ def result ...@@ -48,6 +46,8 @@ def result
private private
attr_reader :results
def process_variables(variables) def process_variables(variables)
filtered_variables = filter_sensitive_variables(variables) filtered_variables = filter_sensitive_variables(variables)
filtered_variables.try(:to_s) || filtered_variables filtered_variables.try(:to_s) || filtered_variables
......
...@@ -10,50 +10,61 @@ class RecursionAnalyzer < GraphQL::Analysis::AST::Analyzer ...@@ -10,50 +10,61 @@ class RecursionAnalyzer < GraphQL::Analysis::AST::Analyzer
IGNORED_FIELDS = %w(node edges nodes ofType).freeze IGNORED_FIELDS = %w(node edges nodes ofType).freeze
RECURSION_THRESHOLD = 2 RECURSION_THRESHOLD = 2
def initial_value(query) def initialize(query)
{ super
recurring_fields: {}
} @node_visits = {}
@recurring_fields = {}
end end
def call(memo, visit_type, irep_node) def on_enter_field(node, _parent, visitor)
return memo if skip_node?(irep_node) return if skip_node?(node, visitor)
node_name = irep_node.ast_node.name node_name = node.name
times_encountered = memo[node_name] || 0 node_visits[node_name] ||= 0
node_visits[node_name] += 1
if visit_type == :enter times_encountered = @node_visits[node_name]
times_encountered += 1 recurring_fields[node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered)
memo[:recurring_fields][node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered) end
else
times_encountered -= 1 # Visitors are all defined on the AST::Analyzer base class
end # We override them for custom analyzers.
def on_leave_field(node, _parent, visitor)
return if skip_node?(node, visitor)
memo[node_name] = times_encountered node_name = node.name
memo node_visits[node_name] ||= 0
node_visits[node_name] -= 1
end end
def final_value(memo) def result
recurring_fields = memo[:recurring_fields] @recurring_fields = @recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
recurring_fields = recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
if recurring_fields.any? if @recurring_fields.any?
GraphQL::AnalysisError.new("Recursive query - too many of fields '#{recurring_fields}' detected in single branch of the query") GraphQL::AnalysisError.new("Recursive query - too many of fields '#{@recurring_fields}' detected in single branch of the query")
end end
end end
private private
attr_reader :node_visits, :recurring_fields
def recursion_too_deep?(node_name, times_encountered) def recursion_too_deep?(node_name, times_encountered)
return if IGNORED_FIELDS.include?(node_name) return if IGNORED_FIELDS.include?(node_name)
times_encountered > recursion_threshold times_encountered > recursion_threshold
end end
def skip_node?(irep_node) def skip_node?(node, visitor)
ast_node = irep_node.ast_node # We don't want to count skipped fields or fields
!ast_node.is_a?(GraphQL::Language::Nodes::Field) || ast_node.selections.empty? # inside fragment definitions
return false if visitor.skipping? || visitor.visiting_fragment_definition?
!node.is_a?(GraphQL::Language::Nodes::Field) || node.selections.empty?
end end
# separated into a method for use in allow_high_graphql_recursion
def recursion_threshold def recursion_threshold
RECURSION_THRESHOLD RECURSION_THRESHOLD
end 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