diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs index 6cce725e6f6..93ade12e034 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreHostingBuilderExtensions.cs @@ -21,14 +21,14 @@ public static class HotChocolateAspNetCoreHostingBuilderExtensions /// /// The max allowed GraphQL request size. /// - /// - /// Defines if the cost analyzer should be disabled. + /// + /// Defines if the default security policy should be disabled. /// /// public static IRequestExecutorBuilder AddGraphQL( this IHostApplicationBuilder builder, string? schemaName = default, int maxAllowedRequestSize = MaxAllowedRequestSize, - bool disableCostAnalyzer = false) - => builder.Services.AddGraphQLServer(schemaName, maxAllowedRequestSize, disableCostAnalyzer); + bool disableDefaultSecurity = false) + => builder.Services.AddGraphQLServer(schemaName, maxAllowedRequestSize, disableDefaultSecurity); } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs index 110eb35b5bd..562eb42469b 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs @@ -94,8 +94,8 @@ public static IServiceCollection AddGraphQLServerCore( /// /// The max allowed GraphQL request size. /// - /// - /// Defines if the cost analyzer should be disabled. + /// + /// Defines if the default security policy should be disabled. /// /// /// Returns the so that configuration can be chained. @@ -104,7 +104,7 @@ public static IRequestExecutorBuilder AddGraphQLServer( this IServiceCollection services, string? schemaName = default, int maxAllowedRequestSize = MaxAllowedRequestSize, - bool disableCostAnalyzer = false) + bool disableDefaultSecurity = false) { var builder = services .AddGraphQLServerCore(maxAllowedRequestSize) @@ -112,14 +112,20 @@ public static IRequestExecutorBuilder AddGraphQLServer( .AddDefaultHttpRequestInterceptor() .AddSubscriptionServices(); - if (!disableCostAnalyzer) + if (!disableDefaultSecurity) { builder.AddCostAnalyzer(); builder.AddIntrospectionAllowedRule( (sp, _) => { var environment = sp.GetService(); - return (environment?.IsDevelopment() ?? true) == false; + return environment?.IsDevelopment() == false; + }); + builder.AddMaxAllowedFieldCycleDepthRule( + isEnabled: (sp, _) => + { + var environment = sp.GetService(); + return environment?.IsDevelopment() == false; }); } @@ -145,7 +151,7 @@ public static IRequestExecutorBuilder AddGraphQLServer( this IRequestExecutorBuilder builder, string? schemaName = default, bool disableCostAnalyzer = false) - => builder.Services.AddGraphQLServer(schemaName, disableCostAnalyzer: disableCostAnalyzer); + => builder.Services.AddGraphQLServer(schemaName, disableDefaultSecurity: disableCostAnalyzer); /// /// Registers the GraphQL Upload Scalar. diff --git a/src/HotChocolate/Caching/test/Caching.Tests/SchemaTests.cs b/src/HotChocolate/Caching/test/Caching.Tests/SchemaTests.cs index 9c87bf56e4e..33dc5c3dd5c 100644 --- a/src/HotChocolate/Caching/test/Caching.Tests/SchemaTests.cs +++ b/src/HotChocolate/Caching/test/Caching.Tests/SchemaTests.cs @@ -13,7 +13,7 @@ public async Task Allow_CacheControl_On_FieldDefinition() { var schema = await new ServiceCollection() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddTypeExtension(typeof(Query)) .ConfigureSchema( b => b.TryAddRootType( diff --git a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs index 933414ead22..dad7fd68e68 100644 --- a/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs +++ b/src/HotChocolate/Core/src/Abstractions/ErrorCodes.cs @@ -310,6 +310,16 @@ public static class Validation /// The introspection is not allowed for the current request /// public const string IntrospectionNotAllowed = "HC0046"; + + /// + /// The maximum allowed introspection depth was exceeded. + /// + public const string MaxIntrospectionDepthOverflow = "HC0086"; + + /// + /// The maximum allowed coordinate cycle depth was exceeded. + /// + public const string MaxCoordinateCycleDepthOverflow = "HC0087"; } /// diff --git a/src/HotChocolate/Core/src/Authorization/AuthorizeValidationRule.cs b/src/HotChocolate/Core/src/Authorization/AuthorizeValidationRule.cs index c990879a054..4b95641c9f5 100644 --- a/src/HotChocolate/Core/src/Authorization/AuthorizeValidationRule.cs +++ b/src/HotChocolate/Core/src/Authorization/AuthorizeValidationRule.cs @@ -8,6 +8,8 @@ internal sealed class AuthorizeValidationRule(AuthorizationCache cache) : IDocum private readonly AuthorizeValidationVisitor _visitor = new(); private readonly AuthorizationCache _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + public ushort Priority => ushort.MaxValue; + public bool IsCacheable => false; public void Validate(IDocumentValidatorContext context, DocumentNode document) diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Validation.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Validation.cs index 07cf4be931e..ab5066b6719 100644 --- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Validation.cs +++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorBuilderExtensions.Validation.cs @@ -1,3 +1,4 @@ +using HotChocolate; using HotChocolate.Execution.Configuration; using HotChocolate.Validation; using HotChocolate.Validation.Options; @@ -187,6 +188,9 @@ public static IRequestExecutorBuilder AddValidationResultAggregator( /// /// Defines if request depth overrides are allowed on a per request basis. /// + /// + /// Defines if the validation rule is enabled. + /// /// /// Returns the for configuration chaining. /// @@ -194,7 +198,8 @@ public static IRequestExecutorBuilder AddMaxExecutionDepthRule( this IRequestExecutorBuilder builder, int maxAllowedExecutionDepth, bool skipIntrospectionFields = false, - bool allowRequestOverrides = false) + bool allowRequestOverrides = false, + Func? isEnabled = null) { if (builder is null) { @@ -206,7 +211,8 @@ public static IRequestExecutorBuilder AddMaxExecutionDepthRule( b => b.AddMaxExecutionDepthRule( maxAllowedExecutionDepth, skipIntrospectionFields, - allowRequestOverrides)); + allowRequestOverrides, + isEnabled)); return builder; } @@ -280,6 +286,45 @@ public static IRequestExecutorBuilder SetMaxAllowedValidationErrors( return builder; } + /// + /// Adds a validation rule that restricts the coordinate cycle depth in a GraphQL operation. + /// + public static IRequestExecutorBuilder AddMaxAllowedFieldCycleDepthRule( + this IRequestExecutorBuilder builder, + ushort? defaultCycleLimit = 3, + (SchemaCoordinate Coordinate, ushort MaxAllowed)[]? coordinateCycleLimits = null, + Func? isEnabled = null) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + ConfigureValidation( + builder, + b => b.AddMaxAllowedFieldCycleDepthRule( + defaultCycleLimit, + coordinateCycleLimits, + isEnabled)); + + return builder; + } + + /// + /// Removes the validation rule that restricts the coordinate cycle depth in a GraphQL operation. + /// + public static IRequestExecutorBuilder RemoveMaxAllowedFieldCycleDepthRule( + this IRequestExecutorBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + ConfigureValidation(builder, b => b.RemoveMaxAllowedFieldCycleDepthRule()); + return builder; + } + private static IRequestExecutorBuilder ConfigureValidation( IRequestExecutorBuilder builder, Action configure) diff --git a/src/HotChocolate/Core/src/Validation/CoordinateLimit.cs b/src/HotChocolate/Core/src/Validation/CoordinateLimit.cs new file mode 100644 index 00000000000..2717b7cc7bd --- /dev/null +++ b/src/HotChocolate/Core/src/Validation/CoordinateLimit.cs @@ -0,0 +1,27 @@ +namespace HotChocolate.Validation; + +internal sealed class CoordinateLimit +{ + public ushort MaxAllowed { get; private set; } + + public ushort Count { get; private set; } + + public bool Add() + { + if (Count < MaxAllowed) + { + Count++; + return true; + } + + return false; + } + + public void Remove() => Count--; + + public void Reset(ushort maxAllowed) + { + MaxAllowed = maxAllowed; + Count = 0; + } +} diff --git a/src/HotChocolate/Core/src/Validation/DocumentValidator.cs b/src/HotChocolate/Core/src/Validation/DocumentValidator.cs index 32362f8da70..fcf2f556b92 100644 --- a/src/HotChocolate/Core/src/Validation/DocumentValidator.cs +++ b/src/HotChocolate/Core/src/Validation/DocumentValidator.cs @@ -54,6 +54,9 @@ public DocumentValidator( _nonCacheableRules = _allRules.Where(t => !t.IsCacheable).ToArray(); _aggregators = resultAggregators.ToArray(); _maxAllowedErrors = errorOptions.MaxAllowedErrors; + + Array.Sort(_allRules, (a, b) => a.Priority.CompareTo(b.Priority)); + Array.Sort(_nonCacheableRules, (a, b) => a.Priority.CompareTo(b.Priority)); } /// @@ -102,6 +105,11 @@ public ValueTask ValidateAsync( for (var i = 0; i < length; i++) { Unsafe.Add(ref start, i).Validate(context, document); + + if (context.FatalErrorDetected) + { + break; + } } if (_aggregators.Length == 0) diff --git a/src/HotChocolate/Core/src/Validation/DocumentValidatorContext.cs b/src/HotChocolate/Core/src/Validation/DocumentValidatorContext.cs index 9b645c35b54..f0c913368a1 100644 --- a/src/HotChocolate/Core/src/Validation/DocumentValidatorContext.cs +++ b/src/HotChocolate/Core/src/Validation/DocumentValidatorContext.cs @@ -96,6 +96,8 @@ public IOutputType NonNullString public bool UnexpectedErrorsDetected { get; set; } + public bool FatalErrorDetected { get; set; } + public int Count { get; set; } public int Max { get; set; } @@ -110,6 +112,8 @@ public IOutputType NonNullString public HashSet ProcessedFieldPairs { get; } = []; + public FieldDepthCycleTracker FieldDepth { get; } = new(); + public IList RentFieldInfoList() { var buffer = _buffers.Peek(); @@ -166,7 +170,9 @@ public void Clear() CurrentFieldPairs.Clear(); NextFieldPairs.Clear(); ProcessedFieldPairs.Clear(); + FieldDepth.Reset(); UnexpectedErrorsDetected = false; + FatalErrorDetected = false; Count = 0; Max = 0; Allowed = 0; diff --git a/src/HotChocolate/Core/src/Validation/DocumentValidatorRule.cs b/src/HotChocolate/Core/src/Validation/DocumentValidatorRule.cs index 49fac8dd123..c6935a7654b 100644 --- a/src/HotChocolate/Core/src/Validation/DocumentValidatorRule.cs +++ b/src/HotChocolate/Core/src/Validation/DocumentValidatorRule.cs @@ -8,12 +8,18 @@ public class DocumentValidatorRule { private readonly TVisitor _visitor; - public DocumentValidatorRule(TVisitor visitor, bool isCacheable = true) + public DocumentValidatorRule( + TVisitor visitor, + bool isCacheable = true, + ushort property = ushort.MaxValue) { _visitor = visitor; IsCacheable = isCacheable; + Priority = property; } + public ushort Priority { get; } + public bool IsCacheable { get; } public void Validate(IDocumentValidatorContext context, DocumentNode document) diff --git a/src/HotChocolate/Core/src/Validation/ErrorHelper.cs b/src/HotChocolate/Core/src/Validation/ErrorHelper.cs index e20a4671a79..e1984388a14 100644 --- a/src/HotChocolate/Core/src/Validation/ErrorHelper.cs +++ b/src/HotChocolate/Core/src/Validation/ErrorHelper.cs @@ -707,4 +707,33 @@ public static IError StreamOnNonListField( .SpecifiedBy("sec-Stream-Directives-Are-Used-On-List-Fields") .SetPath(context.CreateErrorPath()) .Build(); + + public static void ReportMaxIntrospectionDepthOverflow( + this IDocumentValidatorContext context, + ISyntaxNode selection) + { + context.FatalErrorDetected = true; + context.ReportError( + ErrorBuilder.New() + .SetMessage("Maximum allowed introspection depth exceeded.") + .SetCode(ErrorCodes.Validation.MaxIntrospectionDepthOverflow) + .SetLocations([selection]) + .SetPath(context.CreateErrorPath()) + .Build()); + } + + public static void ReportMaxCoordinateCycleDepthOverflow( + this IDocumentValidatorContext context, + ISyntaxNode selection) + { + context.FatalErrorDetected = true; + + context.ReportError( + ErrorBuilder.New() + .SetMessage("Maximum allowed coordinate cycle depth was exceeded.") + .SetCode(ErrorCodes.Validation.MaxIntrospectionDepthOverflow) + .SetLocations([selection]) + .SetPath(context.CreateErrorPath()) + .Build()); + } } diff --git a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs index e8ddc3f5cb2..c0439f486da 100644 --- a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs +++ b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs @@ -1,3 +1,5 @@ +using System.Collections.Immutable; +using HotChocolate; using HotChocolate.Validation.Options; using HotChocolate.Validation.Rules; @@ -111,7 +113,7 @@ public static IValidationBuilder AddDocumentRules( /// Field selections on scalars or enums are never allowed, /// because they are the leaf nodes of any GraphQL query. /// - /// Conversely the leaf field selections of GraphQL queries + /// Conversely, the leaf field selections of GraphQL queries /// must be of type scalar or enum. Leaf selections on objects, /// interfaces, and unions without subfields are disallowed. /// @@ -169,7 +171,7 @@ public static IValidationBuilder AddFieldRules( /// AND /// /// The graph of fragment spreads must not form any cycles including - /// spreading itself. Otherwise an operation could infinitely spread or + /// spreading itself. Otherwise, an operation could infinitely spread or /// infinitely execute on cycles in the underlying data. /// /// https://spec.graphql.org/June2018/#sec-Fragment-spreads-must-not-form-cycles @@ -312,7 +314,10 @@ public static IValidationBuilder AddOperationRules( /// Specifies if depth analysis is skipped for introspection queries. /// /// - /// Defines if request depth overrides are allowed on a per request basis. + /// Defines if request depth overrides are allowed on a per-request basis. + /// + /// + /// A delegate that defines if the rule is enabled. /// /// /// Returns the for configuration chaining. @@ -321,12 +326,15 @@ public static IValidationBuilder AddMaxExecutionDepthRule( this IValidationBuilder builder, int maxAllowedExecutionDepth, bool skipIntrospectionFields = false, - bool allowRequestOverrides = false) + bool allowRequestOverrides = false, + Func? isEnabled = null) { return builder .TryAddValidationVisitor( (_, o) => new MaxExecutionDepthVisitor(o), - isCacheable: !allowRequestOverrides) + priority: 2, + isCacheable: !allowRequestOverrides, + isEnabled: isEnabled) .ModifyValidationOptions(o => { o.MaxAllowedExecutionDepth = maxAllowedExecutionDepth; @@ -334,6 +342,13 @@ public static IValidationBuilder AddMaxExecutionDepthRule( }); } + /// + /// Removes a validation rule that restricts the depth of a GraphQL request. + /// + public static IValidationBuilder RemoveMaxExecutionDepthRule( + this IValidationBuilder builder) + => builder.TryRemoveValidationVisitor(); + /// /// Adds a validation rule that only allows requests to use `__schema` or `__type` /// if the request carries an introspection allowed flag. @@ -341,14 +356,47 @@ public static IValidationBuilder AddMaxExecutionDepthRule( public static IValidationBuilder AddIntrospectionAllowedRule( this IValidationBuilder builder, Func? isEnabled = null) - => builder.TryAddValidationVisitor((_, _) => new IntrospectionVisitor(), false, isEnabled); + => builder.TryAddValidationVisitor( + (_, _) => new IntrospectionVisitor(), + priority: 0, + isCacheable: false, + isEnabled: isEnabled); /// /// Removes a validation rule that only allows requests to use `__schema` or `__type` /// if the request carries an introspection allowed flag. /// public static IValidationBuilder RemoveIntrospectionAllowedRule( + this IValidationBuilder builder) + => builder.TryRemoveValidationVisitor(); + + /// + /// Adds a validation rule that restricts the depth of a GraphQL introspection request. + /// + public static IValidationBuilder AddIntrospectionDepthRule( + this IValidationBuilder builder) + => builder.TryAddValidationVisitor(priority: 1); + + /// + /// Adds a validation rule that restricts the depth of coordinate cycles in GraphQL operations. + /// + public static IValidationBuilder AddMaxAllowedFieldCycleDepthRule( this IValidationBuilder builder, + ushort? defaultCycleLimit = 3, + (SchemaCoordinate Coordinate, ushort MaxAllowed)[]? coordinateCycleLimits = null, Func? isEnabled = null) - => builder.TryRemoveValidationVisitor(); + => builder.TryAddValidationVisitor( + (_, _) => new MaxAllowedFieldCycleDepthVisitor( + coordinateCycleLimits?.ToImmutableArray() + ?? ImmutableArray<(SchemaCoordinate, ushort)>.Empty, + defaultCycleLimit), + priority: 3, + isEnabled: isEnabled); + + /// + /// Removes a validation rule that restricts the depth of coordinate cycles in GraphQL operations. + /// + public static IValidationBuilder RemoveMaxAllowedFieldCycleDepthRule( + this IValidationBuilder builder) + => builder.TryRemoveValidationVisitor(); } diff --git a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.cs b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.cs index 477835a3e3d..887fb2b555d 100644 --- a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.cs +++ b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.cs @@ -110,13 +110,17 @@ internal static IValidationBuilder ModifyValidationOptions( /// Specifies if the validation visitor`s results are cacheable or /// if the visitor needs to be rerun on every request. /// + /// + /// The priority of the validation visitor. The lower the value the earlier the visitor is executed. + /// /// The validation visitor type. /// /// Returns the validation builder for configuration chaining. /// public static IValidationBuilder TryAddValidationVisitor( this IValidationBuilder builder, - bool isCacheable = true) + bool isCacheable = true, + ushort priority = ushort.MaxValue) where T : DocumentValidatorVisitor, new() { return builder.ConfigureValidation(m => @@ -124,7 +128,7 @@ public static IValidationBuilder TryAddValidationVisitor( { if (o.Rules.All(t => t.GetType() != typeof(DocumentValidatorRule))) { - o.Rules.Add(new DocumentValidatorRule(new T(), isCacheable)); + o.Rules.Add(new DocumentValidatorRule(new T(), isCacheable, priority)); } })); } @@ -143,6 +147,9 @@ public static IValidationBuilder TryAddValidationVisitor( /// Specifies if the validation visitor`s results are cacheable or /// if the visitor needs to be rerun on every request. /// + /// + /// The priority of the validation visitor. The lower the value the earlier the visitor is executed. + /// /// /// A delegate to determine if the validation visitor and should be added. /// @@ -154,6 +161,7 @@ public static IValidationBuilder TryAddValidationVisitor( this IValidationBuilder builder, Func factory, bool isCacheable = true, + ushort priority = ushort.MaxValue, Func? isEnabled = null) where T : DocumentValidatorVisitor { @@ -163,7 +171,7 @@ public static IValidationBuilder TryAddValidationVisitor( if (o.Rules.All(t => t.GetType() != typeof(DocumentValidatorRule)) && (isEnabled?.Invoke(s, o) ?? true)) { - o.Rules.Add(new DocumentValidatorRule(factory(s, o), isCacheable)); + o.Rules.Add(new DocumentValidatorRule(factory(s, o), isCacheable, priority)); } })); } diff --git a/src/HotChocolate/Core/src/Validation/Extensions/ValidationServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Validation/Extensions/ValidationServiceCollectionExtensions.cs index 5ab7538b971..308c8800bea 100644 --- a/src/HotChocolate/Core/src/Validation/Extensions/ValidationServiceCollectionExtensions.cs +++ b/src/HotChocolate/Core/src/Validation/Extensions/ValidationServiceCollectionExtensions.cs @@ -28,6 +28,7 @@ public static IValidationBuilder AddValidation( var builder = new DefaultValidationBuilder(schemaName, services); builder + .AddIntrospectionDepthRule() .AddDocumentRules() .AddOperationRules() .AddFieldRules() diff --git a/src/HotChocolate/Core/src/Validation/FieldDepthCycleTracker.cs b/src/HotChocolate/Core/src/Validation/FieldDepthCycleTracker.cs new file mode 100644 index 00000000000..7e001ba2073 --- /dev/null +++ b/src/HotChocolate/Core/src/Validation/FieldDepthCycleTracker.cs @@ -0,0 +1,89 @@ +using HotChocolate.Language; + +namespace HotChocolate.Validation; + +/// +/// Allows to track field cycle depths in a GraphQL query. +/// +public sealed class FieldDepthCycleTracker +{ + private readonly Dictionary _coordinates = new(); + private readonly List _limits = new(); + private ushort? _defaultMaxAllowed; + + /// + /// Adds a field coordinate to the tracker. + /// + /// + /// A field coordinate. + /// + /// + /// true if the field coordinate has not reached its cycle depth limit; + /// otherwise, false. + /// + public bool Add(SchemaCoordinate coordinate) + { + if (_coordinates.TryGetValue(coordinate, out var limit)) + { + return limit.Add(); + } + + if(_defaultMaxAllowed.HasValue) + { + _limits.TryPop(out limit); + limit ??= new CoordinateLimit(); + limit.Reset(_defaultMaxAllowed.Value); + _coordinates.Add(coordinate, limit); + return limit.Add(); + } + + return true; + } + + /// + /// Removes a field coordinate from the tracker. + /// + /// + /// A field coordinate. + /// + public void Remove(SchemaCoordinate coordinate) + { + if (_coordinates.TryGetValue(coordinate, out var limit)) + { + limit.Remove(); + } + } + + /// + /// Initializes the field depth tracker with the specified limits. + /// + /// + /// A collection of field coordinates and their cycle depth limits. + /// + /// + /// The default cycle depth limit for coordinates that were not explicitly defined. + /// + public void Initialize( + IEnumerable<(SchemaCoordinate Coordinate, ushort MaxAllowed)> limits, + ushort? defaultMaxAllowed = null) + { + foreach (var (coordinate, maxAllowed) in limits) + { + _limits.TryPop(out var limit); + limit ??= new CoordinateLimit(); + limit.Reset(maxAllowed); + _coordinates.Add(coordinate, limit); + } + + _defaultMaxAllowed = defaultMaxAllowed; + } + + /// + /// Resets the field depth tracker. + /// + public void Reset() + { + _limits.AddRange(_coordinates.Values); + _coordinates.Clear(); + } +} diff --git a/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs b/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs index b923a9e6c94..99402f7ad9a 100644 --- a/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs +++ b/src/HotChocolate/Core/src/Validation/IDocumentValidatorContext.cs @@ -153,6 +153,11 @@ public interface IDocumentValidatorContext /// bool UnexpectedErrorsDetected { get; set; } + /// + /// Defines that a fatal error was detected and that the analyzer will be aborted. + /// + bool FatalErrorDetected { get; set; } + /// /// A map to store arbitrary visitor data. /// @@ -161,17 +166,22 @@ public interface IDocumentValidatorContext /// /// When processing field merging this list holds the field pairs that are processed. /// - List CurrentFieldPairs { get; } + List CurrentFieldPairs { get; } /// /// When processing field merging this list holds the field pairs that are processed next. /// - List NextFieldPairs { get; } + List NextFieldPairs { get; } /// /// When processing field merging this set represents the already processed field pairs. /// - HashSet ProcessedFieldPairs { get; } + HashSet ProcessedFieldPairs { get; } + + /// + /// Gets the field depth cycle tracker. + /// + FieldDepthCycleTracker FieldDepth { get; } /// /// Rents a list of field infos. diff --git a/src/HotChocolate/Core/src/Validation/IDocumentValidatorRule.cs b/src/HotChocolate/Core/src/Validation/IDocumentValidatorRule.cs index 42eb261eeb2..17162a75005 100644 --- a/src/HotChocolate/Core/src/Validation/IDocumentValidatorRule.cs +++ b/src/HotChocolate/Core/src/Validation/IDocumentValidatorRule.cs @@ -7,6 +7,11 @@ namespace HotChocolate.Validation; /// public interface IDocumentValidatorRule { + /// + /// Gets the priority of this rule. Rules with a lower priority are executed first. + /// + ushort Priority { get; } + /// /// Defines if the result of this rule can be cached and reused on consecutive /// validations of the same GraphQL request document. diff --git a/src/HotChocolate/Core/src/Validation/Rules/ArgumentVisitor.cs b/src/HotChocolate/Core/src/Validation/Rules/ArgumentVisitor.cs index 0355dfc9e76..93989e8c3ed 100644 --- a/src/HotChocolate/Core/src/Validation/Rules/ArgumentVisitor.cs +++ b/src/HotChocolate/Core/src/Validation/Rules/ArgumentVisitor.cs @@ -30,16 +30,10 @@ namespace HotChocolate.Validation.Rules; /// /// http://facebook.github.io/graphql/June2018/#sec-Required-Arguments /// -internal sealed class ArgumentVisitor : TypeDocumentValidatorVisitor +internal sealed class ArgumentVisitor() + : TypeDocumentValidatorVisitor( + new SyntaxVisitorOptions { VisitDirectives = true, }) { - public ArgumentVisitor() - : base(new SyntaxVisitorOptions - { - VisitDirectives = true, - }) - { - } - protected override ISyntaxVisitorAction Enter( FieldNode node, IDocumentValidatorContext context) diff --git a/src/HotChocolate/Core/src/Validation/Rules/DocumentRule.cs b/src/HotChocolate/Core/src/Validation/Rules/DocumentRule.cs index beb698e48b5..c8ce2210229 100644 --- a/src/HotChocolate/Core/src/Validation/Rules/DocumentRule.cs +++ b/src/HotChocolate/Core/src/Validation/Rules/DocumentRule.cs @@ -3,22 +3,28 @@ namespace HotChocolate.Validation.Rules; /// +/// /// GraphQL execution will only consider the executable definitions /// Operation and Fragment. -/// +/// +/// /// Type system definitions and extensions are not executable, /// and are not considered during execution. -/// +/// +/// /// To avoid ambiguity, a document containing TypeSystemDefinition /// is invalid for execution. -/// +/// +/// /// GraphQL documents not intended to be directly executed may /// include TypeSystemDefinition. -/// -/// https://spec.graphql.org/June2018/#sec-Executable-Definitions +/// +/// https://spec.graphql.org/June2018/#sec-Executable-Definitions /// internal sealed class DocumentRule : IDocumentValidatorRule { + public ushort Priority => ushort.MaxValue; + public bool IsCacheable => true; public void Validate(IDocumentValidatorContext context, DocumentNode document) diff --git a/src/HotChocolate/Core/src/Validation/Rules/IntrospectionDepthVisitor.cs b/src/HotChocolate/Core/src/Validation/Rules/IntrospectionDepthVisitor.cs new file mode 100644 index 00000000000..c274f1ab743 --- /dev/null +++ b/src/HotChocolate/Core/src/Validation/Rules/IntrospectionDepthVisitor.cs @@ -0,0 +1,77 @@ +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Types; +using HotChocolate.Types.Introspection; +using HotChocolate.Utilities; + +namespace HotChocolate.Validation.Rules; + +/// +/// This rules ensures that recursive introspection fields cannot be used +/// to create endless cycles. +/// +internal sealed class IntrospectionDepthVisitor : TypeDocumentValidatorVisitor +{ + private readonly (SchemaCoordinate Coordinate, ushort MaxAllowed)[] _limits = + [ + (new SchemaCoordinate("__Type", "fields"), 1), + (new SchemaCoordinate("__Type", "inputFields"), 1), + (new SchemaCoordinate("__Type", "interfaces"), 1), + (new SchemaCoordinate("__Type", "possibleTypes"), 1), + (new SchemaCoordinate("__Type", "ofType"), 8) + ]; + + protected override ISyntaxVisitorAction Enter( + DocumentNode node, + IDocumentValidatorContext context) + { + context.FieldDepth.Initialize(_limits); + return base.Enter(node, context); + } + + protected override ISyntaxVisitorAction Enter( + FieldNode node, + IDocumentValidatorContext context) + { + if (IntrospectionFields.TypeName.EqualsOrdinal(node.Name.Value)) + { + return Skip; + } + + if (context.Types.TryPeek(out var type) + && type.NamedType() is IComplexOutputType ot + && ot.Fields.TryGetField(node.Name.Value, out var of)) + { + // we are only interested in fields if the root field is either + // __type or __schema. + if (context.OutputFields.Count == 0 + && !of.IsIntrospectionField) + { + return Skip; + } + + if (!context.FieldDepth.Add(of.Coordinate)) + { + context.ReportMaxIntrospectionDepthOverflow(node); + return Break; + } + + context.OutputFields.Push(of); + context.Types.Push(of.Type); + return Continue; + } + + context.UnexpectedErrorsDetected = true; + return Skip; + } + + protected override ISyntaxVisitorAction Leave( + FieldNode node, + IDocumentValidatorContext context) + { + context.FieldDepth.Remove(context.OutputFields.Peek().Coordinate); + context.Types.Pop(); + context.OutputFields.Pop(); + return Continue; + } +} diff --git a/src/HotChocolate/Core/src/Validation/Rules/IntrospectionVisitor.cs b/src/HotChocolate/Core/src/Validation/Rules/IntrospectionVisitor.cs index a907972b97d..b0b576f534c 100644 --- a/src/HotChocolate/Core/src/Validation/Rules/IntrospectionVisitor.cs +++ b/src/HotChocolate/Core/src/Validation/Rules/IntrospectionVisitor.cs @@ -36,7 +36,6 @@ protected override ISyntaxVisitorAction Enter( if (context.Types.TryPeek(out var type)) { var namedType = type.NamedType(); - if (context.Schema.QueryType == namedType && (IntrospectionFields.Schema.EqualsOrdinal(node.Name.Value) || IntrospectionFields.Type.EqualsOrdinal(node.Name.Value))) @@ -45,36 +44,10 @@ protected override ISyntaxVisitorAction Enter( return Break; } - if (namedType is IComplexOutputType ct) - { - if (ct.Fields.TryGetField(node.Name.Value, out var of)) - { - if (node.SelectionSet is null || - node.SelectionSet.Selections.Count == 0 || - of.Type.NamedType().IsLeafType()) - { - return Skip; - } - - context.OutputFields.Push(of); - context.Types.Push(of.Type); - return Continue; - } - - return Skip; - } + return Skip; } context.UnexpectedErrorsDetected = true; return Skip; } - - protected override ISyntaxVisitorAction Leave( - FieldNode node, - IDocumentValidatorContext context) - { - context.OutputFields.Pop(); - context.Types.Pop(); - return Continue; - } } diff --git a/src/HotChocolate/Core/src/Validation/Rules/MaxAllowedFieldCycleDepthVisitor.cs b/src/HotChocolate/Core/src/Validation/Rules/MaxAllowedFieldCycleDepthVisitor.cs new file mode 100644 index 00000000000..a59ee506cc7 --- /dev/null +++ b/src/HotChocolate/Core/src/Validation/Rules/MaxAllowedFieldCycleDepthVisitor.cs @@ -0,0 +1,75 @@ +using System.Collections.Immutable; +using HotChocolate.Language; +using HotChocolate.Language.Visitors; +using HotChocolate.Types; +using HotChocolate.Types.Introspection; +using HotChocolate.Utilities; + +namespace HotChocolate.Validation.Rules; + +/// +/// This rules allows to limit cycles across unique field coordinates. +/// +/// +/// Specifies specific coordinate cycle limits. +/// +/// +/// Specifies the default coordinate cycle limit. +/// +internal sealed class MaxAllowedFieldCycleDepthVisitor( + ImmutableArray<(SchemaCoordinate Coordinate, ushort MaxAllowed)> coordinateCycleLimits, + ushort? defaultCycleLimit) + : TypeDocumentValidatorVisitor +{ + protected override ISyntaxVisitorAction Enter( + DocumentNode node, + IDocumentValidatorContext context) + { + context.FieldDepth.Initialize(coordinateCycleLimits, defaultCycleLimit); + return base.Enter(node, context); + } + + protected override ISyntaxVisitorAction Enter( + FieldNode node, + IDocumentValidatorContext context) + { + if (IntrospectionFields.TypeName.EqualsOrdinal(node.Name.Value)) + { + return Skip; + } + + if (context.Types.TryPeek(out var type) + && type.NamedType() is IComplexOutputType ot + && ot.Fields.TryGetField(node.Name.Value, out var of)) + { + // we are ignoring introspection fields in this visitor. + if (of.IsIntrospectionField) + { + return Skip; + } + + if (!context.FieldDepth.Add(of.Coordinate)) + { + context.ReportMaxCoordinateCycleDepthOverflow(node); + return Break; + } + + context.OutputFields.Push(of); + context.Types.Push(of.Type); + return Continue; + } + + context.UnexpectedErrorsDetected = true; + return Skip; + } + + protected override ISyntaxVisitorAction Leave( + FieldNode node, + IDocumentValidatorContext context) + { + context.FieldDepth.Remove(context.OutputFields.Peek().Coordinate); + context.Types.Pop(); + context.OutputFields.Pop(); + return Continue; + } +} diff --git a/src/HotChocolate/Core/src/Validation/Rules/MaxExecutionDepthVisitor.cs b/src/HotChocolate/Core/src/Validation/Rules/MaxExecutionDepthVisitor.cs index b1f5802892f..e9fa0eb7d67 100644 --- a/src/HotChocolate/Core/src/Validation/Rules/MaxExecutionDepthVisitor.cs +++ b/src/HotChocolate/Core/src/Validation/Rules/MaxExecutionDepthVisitor.cs @@ -5,15 +5,10 @@ namespace HotChocolate.Validation.Rules; -internal sealed class MaxExecutionDepthVisitor : DocumentValidatorVisitor +internal sealed class MaxExecutionDepthVisitor( + IMaxExecutionDepthOptionsAccessor options) + : DocumentValidatorVisitor { - private readonly IMaxExecutionDepthOptionsAccessor _options; - - public MaxExecutionDepthVisitor(IMaxExecutionDepthOptionsAccessor options) - { - _options = options; - } - protected override ISyntaxVisitorAction Enter( DocumentNode node, IDocumentValidatorContext context) @@ -33,9 +28,9 @@ protected override ISyntaxVisitorAction Enter( } // otherwise we will go with the configured value - else if(_options.MaxAllowedExecutionDepth.HasValue) + else if(options.MaxAllowedExecutionDepth.HasValue) { - context.Allowed = _options.MaxAllowedExecutionDepth.Value; + context.Allowed = options.MaxAllowedExecutionDepth.Value; } // if there is no configured value we will just stop traversing the graph @@ -80,7 +75,7 @@ protected override ISyntaxVisitorAction Enter( FieldNode node, IDocumentValidatorContext context) { - if (_options.SkipIntrospectionFields && + if (options.SkipIntrospectionFields && node.Name.Value.StartsWith("__")) { return Skip; diff --git a/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_Validation.Tests.cs b/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_Validation.Tests.cs index dc08689167b..07415f0c22e 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_Validation.Tests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/DependencyInjection/RequestExecutorBuilderExtensions_Validation.Tests.cs @@ -207,6 +207,7 @@ public class MockVisitor : DocumentValidatorVisitor; public class MockRule : IDocumentValidatorRule { + public ushort Priority => ushort.MaxValue; public bool IsCacheable => true; public void Validate(IDocumentValidatorContext context, DocumentNode document) diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/IntegrationTests.cs index 80bd0bd36d4..497e0886829 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/IntegrationTests.cs @@ -146,7 +146,7 @@ private static IServiceProvider CreateApplicationServices( .AddSingleton(); serviceCollection - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddCustomModule() .AddGlobalObjectIdentification() .AddMutationConventions(); diff --git a/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorTests.cs b/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorTests.cs index bea9c2ece04..9744480ffe7 100644 --- a/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorTests.cs +++ b/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorTests.cs @@ -861,6 +861,12 @@ public async Task Produce_Many_Errors_50000_query() await ExpectErrors(FileResource.Open("50000_query.graphql")); } + [Fact] + public async Task Introspection_Cycle_Detected() + { + await ExpectErrors(FileResource.Open("introspection_with_cycle.graphql")); + } + private Task ExpectValid(string sourceText) => ExpectValid(null, null, sourceText); private async Task ExpectValid(ISchema? schema, IDocumentValidator? validator, string sourceText) diff --git a/src/HotChocolate/Core/test/Validation.Tests/HotChocolate.Validation.Tests.csproj b/src/HotChocolate/Core/test/Validation.Tests/HotChocolate.Validation.Tests.csproj index ca7f3e84ba0..33e66b35bc2 100644 --- a/src/HotChocolate/Core/test/Validation.Tests/HotChocolate.Validation.Tests.csproj +++ b/src/HotChocolate/Core/test/Validation.Tests/HotChocolate.Validation.Tests.csproj @@ -18,5 +18,8 @@ Always + + Always + diff --git a/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs b/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs new file mode 100644 index 00000000000..4f682209691 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs @@ -0,0 +1,205 @@ +using CookieCrumble; +using HotChocolate.Language; +using HotChocolate.Validation.Options; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Validation; + +public class IntrospectionDepthRuleTests() + : DocumentValidatorVisitorTestBase(b => b.AddIntrospectionDepthRule()) +{ + [Fact] public void Introspection_With_Cycles_Will_Fail() + { + // arrange + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse(FileResource.Open("introspection_with_cycle.graphql")); + context.Prepare(query); + + // act + Rule.Validate(context, query); + + // assert + Assert.Equal( + "Maximum allowed introspection depth exceeded.", + Assert.Single(context.Errors).Message); + } + + [Fact] + public void Introspection_Without_Cycles() + { + // arrange + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse(FileResource.Open("introspection_without_cycle.graphql")); + context.Prepare(query); + + // act + Rule.Validate(context, query); + + // assert + Assert.Empty(context.Errors); + } +} + +public class MaxAllowedFieldCycleDepthRuleTests() + : DocumentValidatorVisitorTestBase(b => b.AddMaxAllowedFieldCycleDepthRule()) +{ + [Fact] + public void Max_3_Cycles_Allowed_Success() + { + // arrange + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse( + """ + { + human { + relatives { + relatives { + relatives { + name + } + } + } + } + } + """); + context.Prepare(query); + + // act + Rule.Validate(context, query); + + // assert + Assert.Empty(context.Errors); + Assert.False(context.FatalErrorDetected); + Assert.False(context.UnexpectedErrorsDetected); + } + + [Fact] + public void Max_3_Cycles_Allowed_Fail() + { + // arrange + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse( + """ + { + human { + relatives { + relatives { + relatives { + relatives { + name + } + } + } + } + } + } + """); + context.Prepare(query); + + // act + Rule.Validate(context, query); + + // assert + Assert.Equal( + "Maximum allowed coordinate cycle depth was exceeded.", + Assert.Single(context.Errors).Message); + Assert.True(context.FatalErrorDetected); + Assert.False(context.UnexpectedErrorsDetected); + } + + [Fact] + public void Max_2_Relative_Field_Allowed_Success() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var builder = serviceCollection + .AddValidation() + .ConfigureValidation(c => c.Modifiers.Add(o => o.Rules.Clear())) + .ModifyValidationOptions(o => o.MaxAllowedErrors = int.MaxValue); + builder.AddMaxAllowedFieldCycleDepthRule( + null, + [(new SchemaCoordinate("Human", "relatives"), 2)]); + + IServiceProvider services = serviceCollection.BuildServiceProvider(); + + var rule = services + .GetRequiredService() + .GetRules(Schema.DefaultName).First(); + + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse( + """ + { + human { + relatives { + relatives { + name + } + } + } + } + """); + context.Prepare(query); + + // act + rule.Validate(context, query); + + // assert + Assert.Empty(context.Errors); + Assert.False(context.FatalErrorDetected); + Assert.False(context.UnexpectedErrorsDetected); + } + + [Fact] + public void Max_1_Relative_Field_Allowed_Fail() + { + // arrange + var serviceCollection = new ServiceCollection(); + + var builder = serviceCollection + .AddValidation() + .ConfigureValidation(c => c.Modifiers.Add(o => o.Rules.Clear())) + .ModifyValidationOptions(o => o.MaxAllowedErrors = int.MaxValue); + builder.AddMaxAllowedFieldCycleDepthRule( + null, + [(new SchemaCoordinate("Human", "relatives"), 1)]); + + IServiceProvider services = serviceCollection.BuildServiceProvider(); + + var rule = services + .GetRequiredService() + .GetRules(Schema.DefaultName).First(); + + IDocumentValidatorContext context = ValidationUtils.CreateContext(); + + var query = Utf8GraphQLParser.Parse( + """ + { + human { + relatives { + relatives { + name + } + } + } + } + """); + context.Prepare(query); + + // act + rule.Validate(context, query); + + // assert + Assert.Equal( + "Maximum allowed coordinate cycle depth was exceeded.", + Assert.Single(context.Errors).Message); + Assert.True(context.FatalErrorDetected); + Assert.False(context.UnexpectedErrorsDetected); + } +} diff --git a/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_with_cycle.graphql b/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_with_cycle.graphql new file mode 100644 index 00000000000..ac0a3e54237 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_with_cycle.graphql @@ -0,0 +1,93 @@ +query IntrospectionQuery { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onOperation + onFragment + onField + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + fields { + name + } + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_without_cycle.graphql b/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_without_cycle.graphql new file mode 100644 index 00000000000..bab1d210a34 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__resources__/introspection_without_cycle.graphql @@ -0,0 +1,90 @@ +query IntrospectionQuery { + __schema { + queryType { + name + } + mutationType { + name + } + types { + ... FullType + } + directives { + name + description + args { + ... InputValue + } + onOperation + onFragment + onField + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ... InputValue + } + type { + ... TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ... InputValue + } + interfaces { + ... TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ... TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ... TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } +} diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/DocumentValidatorTests.Introspection_Cycle_Detected.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/DocumentValidatorTests.Introspection_Cycle_Detected.snap new file mode 100644 index 00000000000..5599ef7c648 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/DocumentValidatorTests.Introspection_Cycle_Detected.snap @@ -0,0 +1,41 @@ +[ + { + "Message": "Maximum allowed introspection depth exceeded.", + "Code": "HC0086", + "Path": { + "Name": "type", + "Parent": { + "Name": "fields", + "Parent": { + "Name": "types", + "Parent": { + "Name": "__schema", + "Parent": { + "Parent": null, + "Length": 0, + "IsRoot": true + }, + "Length": 1, + "IsRoot": false + }, + "Length": 2, + "IsRoot": false + }, + "Length": 3, + "IsRoot": false + }, + "Length": 4, + "IsRoot": false + }, + "Locations": [ + { + "Line": 37, + "Column": 7 + } + ], + "Extensions": { + "code": "HC0086" + }, + "Exception": null + } +] diff --git a/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs index bf78691069c..39fd20bca4e 100644 --- a/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs +++ b/src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs @@ -53,7 +53,7 @@ public static FusionGatewayBuilder AddFusionGatewayServer( sp.GetRequiredService())); var builder = services - .AddGraphQLServer(graphName, disableCostAnalyzer: true) + .AddGraphQLServer(graphName, disableDefaultSecurity: true) .UseField(next => next) .AddOperationCompilerOptimizer() .AddOperationCompilerOptimizer() diff --git a/src/HotChocolate/Fusion/test/Shared/DemoProject.cs b/src/HotChocolate/Fusion/test/Shared/DemoProject.cs index a2889806c47..a5abf239d40 100644 --- a/src/HotChocolate/Fusion/test/Shared/DemoProject.cs +++ b/src/HotChocolate/Fusion/test/Shared/DemoProject.cs @@ -92,7 +92,7 @@ public static async Task CreateAsync(CancellationToken ct = default s => s .AddRouting() .AddSingleton() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddMutationType() .AddSubscriptionType() @@ -115,7 +115,7 @@ public static async Task CreateAsync(CancellationToken ct = default s => s .AddRouting() .AddSingleton() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddMutationType() .AddSubscriptionType() @@ -138,7 +138,7 @@ public static async Task CreateAsync(CancellationToken ct = default s => s .AddRouting() .AddSingleton() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddMutationType() .AddMutationConventions() @@ -159,7 +159,7 @@ public static async Task CreateAsync(CancellationToken ct = default s => s .AddRouting() .AddSingleton() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddMutationType() .AddGlobalObjectIdentification() @@ -180,7 +180,7 @@ public static async Task CreateAsync(CancellationToken ct = default var shipping = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .ConfigureSchema(b => b.SetContextData(GlobalIdSupportEnabled, 1)) .AddConvention(_ => new DefaultNamingConventions()), @@ -198,7 +198,7 @@ public static async Task CreateAsync(CancellationToken ct = default var shipping2 = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .ConfigureSchema(b => b.SetContextData(GlobalIdSupportEnabled, 1)) .AddConvention(_ => new DefaultNamingConventions()), @@ -216,7 +216,7 @@ public static async Task CreateAsync(CancellationToken ct = default var appointment = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddObjectType() .AddObjectType() @@ -236,7 +236,7 @@ public static async Task CreateAsync(CancellationToken ct = default var patient1 = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddGlobalObjectIdentification() .AddConvention(_ => new DefaultNamingConventions()), @@ -254,7 +254,7 @@ public static async Task CreateAsync(CancellationToken ct = default var books = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddConvention(_ => new DefaultNamingConventions()), c => c @@ -271,7 +271,7 @@ public static async Task CreateAsync(CancellationToken ct = default var authors = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddConvention(_ => new DefaultNamingConventions()), c => c @@ -288,7 +288,7 @@ public static async Task CreateAsync(CancellationToken ct = default var resale = testServerFactory.Create( s => s .AddRouting() - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddQueryType() .AddGlobalObjectIdentification() .AddMutationConventions() diff --git a/src/HotChocolate/Language/src/Language/ListExtensions.cs b/src/HotChocolate/Language/src/Language/ListExtensions.cs index c4e6bb5cfe3..92ba6f4ab10 100644 --- a/src/HotChocolate/Language/src/Language/ListExtensions.cs +++ b/src/HotChocolate/Language/src/Language/ListExtensions.cs @@ -16,7 +16,7 @@ public static T Pop(this IList list) return p; } - public static bool TryPop(this IList list, [NotNullWhen(true)] out T item) + public static bool TryPop(this IList list, [MaybeNullWhen(false)] out T item) { if (list.Count > 0) { diff --git a/src/HotChocolate/Raven/test/Data.Raven.Tests/AnnotationBasedTests.cs b/src/HotChocolate/Raven/test/Data.Raven.Tests/AnnotationBasedTests.cs index f8b2dc13c1e..bb36949a61f 100644 --- a/src/HotChocolate/Raven/test/Data.Raven.Tests/AnnotationBasedTests.cs +++ b/src/HotChocolate/Raven/test/Data.Raven.Tests/AnnotationBasedTests.cs @@ -242,7 +242,7 @@ public async Task Executable_Should_Work() public ValueTask CreateExecutorAsync() => new ServiceCollection() .AddSingleton(CreateDocumentStore()) - .AddGraphQLServer(disableCostAnalyzer: true) + .AddGraphQLServer(disableDefaultSecurity: true) .AddRavenFiltering() .AddRavenProjections() .AddRavenSorting()