Commit 9f888c74 authored by Sean McGivern's avatar Sean McGivern

Merge branch '58404-set-default-max-depth-for-GraphQL' into 'master'

58404 - setup max depth for graphql

Closes #58404

See merge request gitlab-org/gitlab-ce!25737
parents 69cfdfae f80f68d5
...@@ -7,6 +7,9 @@ class GitlabSchema < GraphQL::Schema ...@@ -7,6 +7,9 @@ class GitlabSchema < GraphQL::Schema
AUTHENTICATED_COMPLEXITY = 250 AUTHENTICATED_COMPLEXITY = 250
ADMIN_COMPLEXITY = 300 ADMIN_COMPLEXITY = 300
ANONYMOUS_MAX_DEPTH = 10
AUTHENTICATED_MAX_DEPTH = 15
use BatchLoader::GraphQL use BatchLoader::GraphQL
use Gitlab::Graphql::Authorize use Gitlab::Graphql::Authorize
use Gitlab::Graphql::Present use Gitlab::Graphql::Present
...@@ -23,13 +26,17 @@ class GitlabSchema < GraphQL::Schema ...@@ -23,13 +26,17 @@ class GitlabSchema < GraphQL::Schema
mutation(Types::MutationType) mutation(Types::MutationType)
def self.execute(query_str = nil, **kwargs) class << self
def execute(query_str = nil, **kwargs)
kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context]) kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
kwargs[:max_depth] ||= max_query_depth(kwargs[:context])
super(query_str, **kwargs) super(query_str, **kwargs)
end end
def self.max_query_complexity(ctx) private
def max_query_complexity(ctx)
current_user = ctx&.fetch(:current_user, nil) current_user = ctx&.fetch(:current_user, nil)
if current_user&.admin if current_user&.admin
...@@ -40,4 +47,15 @@ class GitlabSchema < GraphQL::Schema ...@@ -40,4 +47,15 @@ class GitlabSchema < GraphQL::Schema
DEFAULT_MAX_COMPLEXITY DEFAULT_MAX_COMPLEXITY
end end
end end
def max_query_depth(ctx)
current_user = ctx&.fetch(:current_user, nil)
if current_user
AUTHENTICATED_MAX_DEPTH
else
ANONYMOUS_MAX_DEPTH
end
end
end
end end
---
title: 58404 - setup max depth for GraphQL
merge_request: 25737
author: Ken Ding
type: added
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe GitlabSchema do describe GitlabSchema do
let(:user) { build :user }
it 'uses batch loading' do it 'uses batch loading' do
expect(field_instrumenters).to include(BatchLoader::GraphQL) expect(field_instrumenters).to include(BatchLoader::GraphQL)
end end
...@@ -33,16 +35,20 @@ describe GitlabSchema do ...@@ -33,16 +35,20 @@ describe GitlabSchema do
expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection) expect(connection).to eq(Gitlab::Graphql::Connections::KeysetConnection)
end end
describe '.execute' do
context 'for different types of users' do context 'for different types of users' do
it 'returns DEFAULT_MAX_COMPLEXITY for no context' do context 'when no context' do
it 'returns DEFAULT_MAX_COMPLEXITY' do
expect(GraphQL::Schema) expect(GraphQL::Schema)
.to receive(:execute) .to receive(:execute)
.with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY))
described_class.execute('query') described_class.execute('query')
end end
end
it 'returns DEFAULT_MAX_COMPLEXITY for no user' do context 'when no user' do
it 'returns DEFAULT_MAX_COMPLEXITY' do
expect(GraphQL::Schema) expect(GraphQL::Schema)
.to receive(:execute) .to receive(:execute)
.with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY)) .with('query', hash_including(max_complexity: GitlabSchema::DEFAULT_MAX_COMPLEXITY))
...@@ -50,29 +56,57 @@ describe GitlabSchema do ...@@ -50,29 +56,57 @@ describe GitlabSchema do
described_class.execute('query', context: {}) described_class.execute('query', context: {})
end end
it 'returns AUTHENTICATED_COMPLEXITY for a logged in user' do it 'returns ANONYMOUS_MAX_DEPTH' do
user = build :user expect(GraphQL::Schema)
.to receive(:execute)
.with('query', hash_including(max_depth: GitlabSchema::ANONYMOUS_MAX_DEPTH))
described_class.execute('query', context: {})
end
end
context 'when a logged in user' do
it 'returns AUTHENTICATED_COMPLEXITY' do
expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY)) expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY))
described_class.execute('query', context: { current_user: user }) described_class.execute('query', context: { current_user: user })
end end
it 'returns ADMIN_COMPLEXITY for an admin user' do it 'returns AUTHENTICATED_MAX_DEPTH' do
expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH))
described_class.execute('query', context: { current_user: user })
end
end
context 'when an admin user' do
it 'returns ADMIN_COMPLEXITY' do
user = build :user, :admin user = build :user, :admin
expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY)) expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY))
described_class.execute('query', context: { current_user: user }) described_class.execute('query', context: { current_user: user })
end end
end
context 'when max_complexity passed on the query' do
it 'returns what was passed on the query' do it 'returns what was passed on the query' do
expect(GraphQL::Schema).to receive(:execute).with('query', { max_complexity: 1234 }) expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: 1234))
described_class.execute('query', max_complexity: 1234) described_class.execute('query', max_complexity: 1234)
end end
end end
context 'when max_depth passed on the query' do
it 'returns what was passed on the query' do
expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: 1234))
described_class.execute('query', max_depth: 1234)
end
end
end
end
def field_instrumenters def field_instrumenters
described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins]
end end
......
...@@ -3,16 +3,44 @@ require 'spec_helper' ...@@ -3,16 +3,44 @@ require 'spec_helper'
describe 'GitlabSchema configurations' do describe 'GitlabSchema configurations' do
include GraphqlHelpers include GraphqlHelpers
it 'shows an error if complexity is too high' do let(:project) { create(:project, :repository) }
project = create(:project, :repository) let(:query) { graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) }
query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description)) let(:current_user) { create(:user) }
describe '#max_complexity' do
context 'when complexity is too high' do
it 'shows an error' do
allow(GitlabSchema).to receive(:max_query_complexity).and_return 1 allow(GitlabSchema).to receive(:max_query_complexity).and_return 1
post_graphql(query, current_user: nil) post_graphql(query, current_user: nil)
expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1') expect(graphql_errors.first['message']).to include('which exceeds max complexity of 1')
end end
end
end
describe '#max_depth' do
context 'when query depth is too high' do
it 'shows error' do
errors = [{ "message" => "Query has depth of 2, which exceeds max depth of 1" }]
allow(GitlabSchema).to receive(:max_query_depth).and_return 1
post_graphql(query)
expect(graphql_errors).to eq(errors)
end
end
context 'when query depth is within range' do
it 'has no error' do
allow(GitlabSchema).to receive(:max_query_depth).and_return 5
post_graphql(query)
expect(graphql_errors).to be_nil
end
end
end
context 'when IntrospectionQuery' do context 'when IntrospectionQuery' do
it 'is not too complex' do it 'is not too complex' do
......
...@@ -102,6 +102,7 @@ module GraphqlHelpers ...@@ -102,6 +102,7 @@ module GraphqlHelpers
def all_graphql_fields_for(class_name, parent_types = Set.new) def all_graphql_fields_for(class_name, parent_types = Set.new)
allow_unlimited_graphql_complexity allow_unlimited_graphql_complexity
allow_unlimited_graphql_depth
type = GitlabSchema.types[class_name.to_s] type = GitlabSchema.types[class_name.to_s]
return "" unless type return "" unless type
...@@ -190,4 +191,9 @@ module GraphqlHelpers ...@@ -190,4 +191,9 @@ module GraphqlHelpers
allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil allow_any_instance_of(GitlabSchema).to receive(:max_complexity).and_return nil
allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil allow(GitlabSchema).to receive(:max_query_complexity).with(any_args).and_return nil
end end
def allow_unlimited_graphql_depth
allow_any_instance_of(GitlabSchema).to receive(:max_depth).and_return nil
allow(GitlabSchema).to receive(:max_query_depth).with(any_args).and_return nil
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment