Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make better use of IHasPreferredStatusCode #1140

Merged
merged 4 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Utilize IHasPreferredStatusCode for authentication errors
  • Loading branch information
Shane32 committed Aug 4, 2024
commit f11ef28a6ef119706eefa90f92beb71115c23611
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 5 additions & 1 deletion src/Transports.AspNetCore/Errors/AccessDeniedError.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents an error indicating that the user is not allowed access to the specified resource.
/// </summary>
public class AccessDeniedError : ValidationError
public class AccessDeniedError : ValidationError, IHasPreferredStatusCode
{
/// <inheritdoc cref="AccessDeniedError"/>
public AccessDeniedError(string resource)
Expand All @@ -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).
/// </summary>
public List<string>? RolesRequired { get; set; }

/// <inheritdoc/>
public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.Forbidden;
}
6 changes: 5 additions & 1 deletion src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents an error indicating that the content-type is invalid, for example, could not be parsed or is not supported.
/// </summary>
public class InvalidContentTypeError : RequestError
public class InvalidContentTypeError : RequestError, IHasPreferredStatusCode
{
/// <inheritdoc cref="InvalidContentTypeError"/>
public InvalidContentTypeError() : base("Invalid 'Content-Type' header.") { }

/// <inheritdoc cref="InvalidContentTypeError"/>
public InvalidContentTypeError(string message) : base("Invalid 'Content-Type' header: " + message) { }

/// <inheritdoc/>
public HttpStatusCode PreferredStatusCode { get; set; } = HttpStatusCode.UnsupportedMediaType;
}
11 changes: 5 additions & 6 deletions src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1046,19 +1046,19 @@ await webSocket.CloseAsync(
/// Writes an access denied message to the output with status code <c>401 Unauthorized</c> when the user is not authenticated.
/// </summary>
protected virtual Task HandleNotAuthenticatedAsync(HttpContext context, RequestDelegate next)
=> WriteErrorResponseAsync(context, HttpStatusCode.Unauthorized, new AccessDeniedError("schema"));
=> WriteErrorResponseAsync(context, new AccessDeniedError("schema") { PreferredStatusCode = HttpStatusCode.Unauthorized });

/// <summary>
/// Writes an access denied message to the output with status code <c>403 Forbidden</c> when the user fails the role checks.
/// </summary>
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 });

/// <summary>
/// Writes an access denied message to the output with status code <c>403 Forbidden</c> when the user fails the policy check.
/// </summary>
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 });

/// <summary>
/// Writes a '400 JSON body text could not be parsed.' message to the output.
Expand Down Expand Up @@ -1095,15 +1095,14 @@ protected virtual Task HandleWebSocketSubProtocolNotSupportedAsync(HttpContext c
/// Writes a '415 Invalid Content-Type header: could not be parsed.' message to the output.
/// </summary>
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."));

/// <summary>
/// Writes a '415 Invalid Content-Type header: non-supported media type.' message to the output.
/// </summary>
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}'.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>? RolesRequired { get; set; }
}
public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>? RolesRequired { get; set; }
}
public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>? RolesRequired { get; set; }
}
public class BatchedRequestsNotSupportedError : GraphQL.Execution.RequestError
Expand Down Expand Up @@ -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
{
Expand Down