Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/graphql/sagittarius_schema.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# frozen_string_literal: true

# rubocop:disable GraphQL/MaxComplexitySchema
# rubocop:disable GraphQL/MaxDepthSchema
class SagittariusSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)

default_max_page_size 50
max_depth 20
connections.add(ActiveRecord::Relation, Sagittarius::Graphql::StableConnection)

# For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
use GraphQL::Dataloader

use GraphQL::Schema::AlwaysVisible

# rubocop:enable GraphQL/MaxComplexitySchema
# rubocop:disable Lint/UselessMethodDefinition
# GraphQL-Ruby calls this when something goes wrong while running a query:
def self.type_error(err, context)
Expand Down Expand Up @@ -54,8 +55,6 @@ def self.object_from_id(global_id, query_ctx = nil)

# rubocop:enable Lint/UnusedMethodArgument
end
# rubocop:enable GraphQL/MaxDepthSchema
# rubocop:enable GraphQL/MaxComplexitySchema

if Types::BaseObject.instance_variable_defined?(:@user_ability_types)
Types::BaseObject.remove_instance_variable(:@user_ability_types) # release temporary type map
Expand Down
88 changes: 88 additions & 0 deletions spec/requests/graphql/query/recursive_query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Recursive Query Protection' do
include GraphqlHelpers

let(:current_user) { create(:user) }
let(:organization) { create(:organization) }

before do
create(:namespace_member, namespace: organization.ensure_namespace, user: current_user)
end

context 'with deeply nested recursive query' do
let(:query) do
# Create a query with deep nesting that would cause recursion
# Organization -> Namespace -> Parent (Organization) -> Namespace -> Parent...
nested_levels = 25
nested_query = 'id name'

nested_levels.times do
nested_query = <<~NESTED
id
name
namespace {
id
parent {
... on Organization {
#{nested_query}
}
}
}
NESTED
end

<<~QUERY
query($organizationId: OrganizationID!) {
organization(id: $organizationId) {
#{nested_query}
}
}
QUERY
end

let(:variables) { { organizationId: organization.to_global_id.to_s } }

it 'blocks the query and returns an error' do
post_graphql query, variables: variables, current_user: current_user

expect(graphql_errors).not_to be_nil
expect(graphql_errors).not_to be_empty
expect(graphql_errors.first['message']).to include('depth')
end
end

context 'with reasonably nested query' do
let(:query) do
# A reasonable query with moderate nesting (depth ~7-8)
<<~QUERY
query($organizationId: OrganizationID!) {
organization(id: $organizationId) {
id
name
namespace {
id
parent {
... on Organization {
id
name
}
}
}
}
}
QUERY
end

let(:variables) { { organizationId: organization.to_global_id.to_s } }

it 'allows the query to execute' do
post_graphql query, variables: variables, current_user: current_user

expect(graphql_data_at(:organization)).not_to be_nil
expect(graphql_data_at(:organization, :id)).to eq(organization.to_global_id.to_s)
end
end
end