From f90438c7de1f7d2f27db9e709d9ded4b8f7d2ab7 Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Sat, 4 Feb 2023 21:54:10 +0100 Subject: [PATCH] Fixed that field authorization at the validation level was skipped (#5766) --- .../AuthorizationTypeInterceptor.cs | 6 +++ .../Core/src/Validation/DocumentValidator.cs | 12 ++--- .../AnnotationBasedAuthorizationTests.cs | 50 +++++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/HotChocolate/Core/src/Authorization/AuthorizationTypeInterceptor.cs b/src/HotChocolate/Core/src/Authorization/AuthorizationTypeInterceptor.cs index 272ccda6c24..a9f1d34a2d9 100644 --- a/src/HotChocolate/Core/src/Authorization/AuthorizationTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Authorization/AuthorizationTypeInterceptor.cs @@ -84,6 +84,12 @@ public override void OnBeforeCompleteTypes() // will use to transform the schema authorization. var state = _state = CreateState(); + // copy temporary state to schema state. + if (_context.ContextData.TryGetValue(AuthorizationRequestPolicy, out var value)) + { + _schemaContextData[AuthorizationRequestPolicy] = value; + } + // before we can apply schema transformations we will inspect the object types // to identify the ones that are protected with authorization directives. InspectObjectTypesForAuthDirective(state); diff --git a/src/HotChocolate/Core/src/Validation/DocumentValidator.cs b/src/HotChocolate/Core/src/Validation/DocumentValidator.cs index fb7c5d1de52..31b4b57412d 100644 --- a/src/HotChocolate/Core/src/Validation/DocumentValidator.cs +++ b/src/HotChocolate/Core/src/Validation/DocumentValidator.cs @@ -18,7 +18,7 @@ public sealed class DocumentValidator : IDocumentValidator { private readonly DocumentValidatorContextPool _contextPool; private readonly IDocumentValidatorRule[] _allRules; - private readonly IDocumentValidatorRule[] _nonCashableRules; + private readonly IDocumentValidatorRule[] _nonCachableRules; private readonly IValidationResultAggregator[] _aggregators; private readonly int _maxAllowedErrors; @@ -55,13 +55,13 @@ public sealed class DocumentValidator : IDocumentValidator _contextPool = contextPool ?? throw new ArgumentNullException(nameof(contextPool)); _allRules = rules.ToArray(); - _nonCashableRules = _allRules.Where(t => !t.IsCacheable).ToArray(); + _nonCachableRules = _allRules.Where(t => !t.IsCacheable).ToArray(); _aggregators = resultAggregators.ToArray(); _maxAllowedErrors = errorOptions.MaxAllowedErrors; } /// - public bool HasDynamicRules => _nonCashableRules.Length > 0 || _aggregators.Length > 0; + public bool HasDynamicRules => _nonCachableRules.Length > 0 || _aggregators.Length > 0; /// public ValueTask ValidateAsync( @@ -69,7 +69,7 @@ public sealed class DocumentValidator : IDocumentValidator DocumentNode document, string documentId, IDictionary contextData, - bool onlyNonCashable, + bool onlyNonCachable, CancellationToken cancellationToken = default) { if (schema is null) @@ -87,13 +87,13 @@ public sealed class DocumentValidator : IDocumentValidator throw new ArgumentNullException(nameof(documentId)); } - if (onlyNonCashable && _nonCashableRules.Length == 0 && _aggregators.Length == 0) + if (onlyNonCachable && _nonCachableRules.Length == 0 && _aggregators.Length == 0) { return new(DocumentValidatorResult.Ok); } var context = _contextPool.Get(); - var rules = onlyNonCashable ? _nonCashableRules : _allRules; + var rules = onlyNonCachable ? _nonCachableRules : _allRules; var handleCleanup = true; try diff --git a/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs b/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs index 7d7cd50ac14..870fba0ac68 100644 --- a/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs +++ b/src/HotChocolate/Core/test/Authorization.Tests/AnnotationBasedAuthorizationTests.cs @@ -672,4 +672,54 @@ public sealed class FooDirectiveAttribute : ObjectTypeDescriptorAttribute Type type) => descriptor.Directive(new FooDirective()); } + + [Fact] + public async Task Ensure_Authorization_Works_On_Subscription() + { + var result = + await new ServiceCollection() + .AddGraphQLServer() + .AddQueryType(c => c.Field("n").Resolve("b")) + .AddSubscriptionType() + .AddInMemorySubscriptions() + .AddAuthorizationHandler() + .ExecuteRequestAsync("subscription { onFoo }"); + + result.MatchInlineSnapshot( + """ + { + "errors": [ + { + "message": "The current user is not authorized to access this resource.", + "extensions": { + "code": "AUTH_NOT_AUTHORIZED" + } + } + ] + } + """); + } + + public class Subscription + { + [Authorize(Apply = ApplyPolicy.Validation)] + [Subscribe] + [Topic("Foo")] + public string OnFoo([EventMessage] string message) => message; + } + + public sealed class MockAuth : IAuthorizationHandler + { + public ValueTask AuthorizeAsync( + IMiddlewareContext context, + AuthorizeDirective directive, + CancellationToken cancellationToken = default) + => new(AuthorizeResult.NotAllowed); + + public ValueTask AuthorizeAsync( + AuthorizationContext context, + IReadOnlyList directives, + CancellationToken cancellationToken = default) + => new(AuthorizeResult.NotAllowed); + } }