From f11ef28a6ef119706eefa90f92beb71115c23611 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 4 Aug 2024 16:27:31 -0400 Subject: [PATCH 1/4] Utilize IHasPreferredStatusCode for authentication errors --- .../AuthorizationVisitorBase.Validation.cs | 1 + src/Transports.AspNetCore/Errors/AccessDeniedError.cs | 6 +++++- .../Errors/InvalidContentTypeError.cs | 6 +++++- src/Transports.AspNetCore/GraphQLHttpMiddleware.cs | 11 +++++------ .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++-- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++-- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 6 ++++-- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs b/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs index 43a834a2..451c16cb 100644 --- a/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs +++ b/src/Transports.AspNetCore/AuthorizationVisitorBase.Validation.cs @@ -141,6 +141,7 @@ protected virtual void HandleNodeNotAuthorized(ValidationInfo info) { var resource = GenerateResourceDescription(info); var err = info.Node == null ? new AccessDeniedError(resource) : new AccessDeniedError(resource, info.Context.Document.Source, info.Node); + err.PreferredStatusCode = HttpStatusCode.Unauthorized; info.Context.ReportError(err); } diff --git a/src/Transports.AspNetCore/Errors/AccessDeniedError.cs b/src/Transports.AspNetCore/Errors/AccessDeniedError.cs index 7ad2c685..ba144bb7 100644 --- a/src/Transports.AspNetCore/Errors/AccessDeniedError.cs +++ b/src/Transports.AspNetCore/Errors/AccessDeniedError.cs @@ -1,9 +1,10 @@ + namespace GraphQL.Server.Transports.AspNetCore.Errors; /// /// Represents an error indicating that the user is not allowed access to the specified resource. /// -public class AccessDeniedError : ValidationError +public class AccessDeniedError : ValidationError, IHasPreferredStatusCode { /// public AccessDeniedError(string resource) @@ -29,4 +30,7 @@ public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, param /// Returns the list of role memberships that would allow access to these node(s). /// public List? RolesRequired { get; set; } + + /// + public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.Forbidden; } diff --git a/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs b/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs index 83194aa2..b0d275ff 100644 --- a/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs +++ b/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs @@ -1,13 +1,17 @@ + namespace GraphQL.Server.Transports.AspNetCore.Errors; /// /// Represents an error indicating that the content-type is invalid, for example, could not be parsed or is not supported. /// -public class InvalidContentTypeError : RequestError +public class InvalidContentTypeError : RequestError, IHasPreferredStatusCode { /// public InvalidContentTypeError() : base("Invalid 'Content-Type' header.") { } /// public InvalidContentTypeError(string message) : base("Invalid 'Content-Type' header: " + message) { } + + /// + public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.UnsupportedMediaType; } diff --git a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs index a8ed6d37..5d493710 100644 --- a/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs +++ b/src/Transports.AspNetCore/GraphQLHttpMiddleware.cs @@ -273,7 +273,7 @@ protected virtual async Task InvokeAsync(HttpContext context, RequestDelegate ne } catch (ExecutionError ex) // catches FileCountExceededError, FileSizeExceededError, InvalidMapError { - await WriteErrorResponseAsync(context, ex is IHasPreferredStatusCode sc ? sc.PreferredStatusCode : HttpStatusCode.BadRequest, ex); + await WriteErrorResponseAsync(context, ex); return null; } catch (Exception ex) // catches JSON deserialization exceptions @@ -1046,19 +1046,19 @@ await webSocket.CloseAsync( /// Writes an access denied message to the output with status code 401 Unauthorized when the user is not authenticated. /// protected virtual Task HandleNotAuthenticatedAsync(HttpContext context, RequestDelegate next) - => WriteErrorResponseAsync(context, HttpStatusCode.Unauthorized, new AccessDeniedError("schema")); + => WriteErrorResponseAsync(context, new AccessDeniedError("schema") { PreferredStatusCode = HttpStatusCode.Unauthorized }); /// /// Writes an access denied message to the output with status code 403 Forbidden when the user fails the role checks. /// protected virtual Task HandleNotAuthorizedRoleAsync(HttpContext context, RequestDelegate next) - => WriteErrorResponseAsync(context, HttpStatusCode.Forbidden, new AccessDeniedError("schema") { RolesRequired = _options.AuthorizedRoles }); + => WriteErrorResponseAsync(context, new AccessDeniedError("schema") { RolesRequired = _options.AuthorizedRoles }); /// /// Writes an access denied message to the output with status code 403 Forbidden when the user fails the policy check. /// protected virtual Task HandleNotAuthorizedPolicyAsync(HttpContext context, RequestDelegate next, AuthorizationResult authorizationResult) - => WriteErrorResponseAsync(context, HttpStatusCode.Forbidden, new AccessDeniedError("schema") { PolicyRequired = _options.AuthorizedPolicy, PolicyAuthorizationResult = authorizationResult }); + => WriteErrorResponseAsync(context, new AccessDeniedError("schema") { PolicyRequired = _options.AuthorizedPolicy, PolicyAuthorizationResult = authorizationResult }); /// /// Writes a '400 JSON body text could not be parsed.' message to the output. @@ -1095,7 +1095,7 @@ protected virtual Task HandleWebSocketSubProtocolNotSupportedAsync(HttpContext c /// Writes a '415 Invalid Content-Type header: could not be parsed.' message to the output. /// protected virtual Task HandleContentTypeCouldNotBeParsedErrorAsync(HttpContext context, RequestDelegate next) - => WriteErrorResponseAsync(context, HttpStatusCode.UnsupportedMediaType, new InvalidContentTypeError($"value '{context.Request.ContentType}' could not be parsed.")); + => WriteErrorResponseAsync(context, new InvalidContentTypeError($"value '{context.Request.ContentType}' could not be parsed.")); /// /// Writes a '415 Invalid Content-Type header: non-supported media type.' message to the output. @@ -1103,7 +1103,6 @@ protected virtual Task HandleContentTypeCouldNotBeParsedErrorAsync(HttpContext c protected virtual Task HandleInvalidContentTypeErrorAsync(HttpContext context, RequestDelegate next) => WriteErrorResponseAsync( context, - HttpStatusCode.UnsupportedMediaType, _options.ReadFormOnPost ? new InvalidContentTypeError($"non-supported media type '{context.Request.ContentType}'. Must be '{MEDIATYPE_JSON}', '{MEDIATYPE_GRAPHQL}' or a form body.") : new InvalidContentTypeError($"non-supported media type '{context.Request.ContentType}'. Must be '{MEDIATYPE_JSON}' or '{MEDIATYPE_GRAPHQL}'.") diff --git a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt index d72fac9e..1e58c8e6 100644 --- a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -192,12 +192,13 @@ namespace GraphQL.Server.Transports.AspNetCore } namespace GraphQL.Server.Transports.AspNetCore.Errors { - public class AccessDeniedError : GraphQL.Validation.ValidationError + public class AccessDeniedError : GraphQL.Validation.ValidationError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public AccessDeniedError(string resource) { } public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params GraphQLParser.AST.ASTNode[] nodes) { } public Microsoft.AspNetCore.Authorization.AuthorizationResult? PolicyAuthorizationResult { get; set; } public string? PolicyRequired { get; set; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } public System.Collections.Generic.List? RolesRequired { get; set; } } public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError @@ -227,10 +228,11 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors { System.Net.HttpStatusCode PreferredStatusCode { get; } } - public class InvalidContentTypeError : GraphQL.Execution.RequestError + public class InvalidContentTypeError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public InvalidContentTypeError() { } public InvalidContentTypeError(string message) { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class InvalidMapError : GraphQL.Execution.RequestError { diff --git a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt index 3de02297..14dec908 100644 --- a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -210,12 +210,13 @@ namespace GraphQL.Server.Transports.AspNetCore } namespace GraphQL.Server.Transports.AspNetCore.Errors { - public class AccessDeniedError : GraphQL.Validation.ValidationError + public class AccessDeniedError : GraphQL.Validation.ValidationError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public AccessDeniedError(string resource) { } public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params GraphQLParser.AST.ASTNode[] nodes) { } public Microsoft.AspNetCore.Authorization.AuthorizationResult? PolicyAuthorizationResult { get; set; } public string? PolicyRequired { get; set; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } public System.Collections.Generic.List? RolesRequired { get; set; } } public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError @@ -245,10 +246,11 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors { System.Net.HttpStatusCode PreferredStatusCode { get; } } - public class InvalidContentTypeError : GraphQL.Execution.RequestError + public class InvalidContentTypeError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public InvalidContentTypeError() { } public InvalidContentTypeError(string message) { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class InvalidMapError : GraphQL.Execution.RequestError { diff --git a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt index 2122e599..a1437153 100644 --- a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -192,12 +192,13 @@ namespace GraphQL.Server.Transports.AspNetCore } namespace GraphQL.Server.Transports.AspNetCore.Errors { - public class AccessDeniedError : GraphQL.Validation.ValidationError + public class AccessDeniedError : GraphQL.Validation.ValidationError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public AccessDeniedError(string resource) { } public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params GraphQLParser.AST.ASTNode[] nodes) { } public Microsoft.AspNetCore.Authorization.AuthorizationResult? PolicyAuthorizationResult { get; set; } public string? PolicyRequired { get; set; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } public System.Collections.Generic.List? RolesRequired { get; set; } } public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError @@ -227,10 +228,11 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors { System.Net.HttpStatusCode PreferredStatusCode { get; } } - public class InvalidContentTypeError : GraphQL.Execution.RequestError + public class InvalidContentTypeError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public InvalidContentTypeError() { } public InvalidContentTypeError(string message) { } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class InvalidMapError : GraphQL.Execution.RequestError { From df2139423a337c44aad80c401946daaace5e4bb2 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 4 Aug 2024 16:29:07 -0400 Subject: [PATCH 2/4] Update --- src/Transports.AspNetCore/Errors/FileCountExceededError.cs | 2 +- src/Transports.AspNetCore/Errors/FileSizeExceededError.cs | 2 +- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 4 ++-- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 4 ++-- .../GraphQL.Server.Transports.AspNetCore.approved.txt | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Transports.AspNetCore/Errors/FileCountExceededError.cs b/src/Transports.AspNetCore/Errors/FileCountExceededError.cs index 0d5733cc..79f6a4b9 100644 --- a/src/Transports.AspNetCore/Errors/FileCountExceededError.cs +++ b/src/Transports.AspNetCore/Errors/FileCountExceededError.cs @@ -14,5 +14,5 @@ public FileCountExceededError() } /// - public HttpStatusCode PreferredStatusCode => HttpStatusCode.RequestEntityTooLarge; + public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.RequestEntityTooLarge; } diff --git a/src/Transports.AspNetCore/Errors/FileSizeExceededError.cs b/src/Transports.AspNetCore/Errors/FileSizeExceededError.cs index 67b998c5..9f897d53 100644 --- a/src/Transports.AspNetCore/Errors/FileSizeExceededError.cs +++ b/src/Transports.AspNetCore/Errors/FileSizeExceededError.cs @@ -14,5 +14,5 @@ public FileSizeExceededError() } /// - public HttpStatusCode PreferredStatusCode => HttpStatusCode.RequestEntityTooLarge; + public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.RequestEntityTooLarge; } diff --git a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt index 1e58c8e6..279e2717 100644 --- a/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/net50+net60+net80/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -213,12 +213,12 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors public class FileCountExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileCountExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class FileSizeExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileSizeExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class HttpMethodValidationError : GraphQL.Validation.ValidationError { diff --git a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt index 14dec908..e1a2d4c7 100644 --- a/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp21+netstandard20/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -231,12 +231,12 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors public class FileCountExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileCountExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class FileSizeExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileSizeExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class HttpMethodValidationError : GraphQL.Validation.ValidationError { diff --git a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt index a1437153..6ed0eab4 100644 --- a/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt +++ b/tests/ApiApprovalTests/netcoreapp31/GraphQL.Server.Transports.AspNetCore.approved.txt @@ -213,12 +213,12 @@ namespace GraphQL.Server.Transports.AspNetCore.Errors public class FileCountExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileCountExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class FileSizeExceededError : GraphQL.Execution.RequestError, GraphQL.Server.Transports.AspNetCore.Errors.IHasPreferredStatusCode { public FileSizeExceededError() { } - public System.Net.HttpStatusCode PreferredStatusCode { get; } + public System.Net.HttpStatusCode PreferredStatusCode { get; set; } } public class HttpMethodValidationError : GraphQL.Validation.ValidationError { From 5fd1f5b0297cbc4bb43df0834662877829358298 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 4 Aug 2024 16:31:25 -0400 Subject: [PATCH 3/4] Update --- src/Transports.AspNetCore/Errors/AccessDeniedError.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Transports.AspNetCore/Errors/AccessDeniedError.cs b/src/Transports.AspNetCore/Errors/AccessDeniedError.cs index ba144bb7..b678a8ea 100644 --- a/src/Transports.AspNetCore/Errors/AccessDeniedError.cs +++ b/src/Transports.AspNetCore/Errors/AccessDeniedError.cs @@ -1,4 +1,3 @@ - namespace GraphQL.Server.Transports.AspNetCore.Errors; /// From a37ad828eaa1c5721dbee48e332391c0e57518c4 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Sun, 4 Aug 2024 16:32:13 -0400 Subject: [PATCH 4/4] Update --- src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs b/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs index b0d275ff..8fcc9872 100644 --- a/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs +++ b/src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs @@ -1,4 +1,3 @@ - namespace GraphQL.Server.Transports.AspNetCore.Errors; ///