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

Update middleware with options and new design #774

Merged
merged 52 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
92b04da
Update middleware with options and new design
Shane32 Apr 14, 2022
e2e9196
Add failing check for mutation for get
Shane32 Apr 14, 2022
d22a09f
Update
Shane32 Apr 14, 2022
ea3c1be
Merge from develop
Shane32 Apr 14, 2022
d54d09c
Update
Shane32 Apr 14, 2022
31a2512
Update api approvals
Shane32 Apr 14, 2022
512cd17
Merge branch 'develop' into update_middleware
Shane32 Apr 14, 2022
b505c71
Convert IUserContextBuilder to file-scoped namespace
Shane32 Apr 17, 2022
ca9801a
Enable NRT for app builder extensions
Shane32 Apr 17, 2022
7c79351
Enable NRT for IUserContextBuilder
Shane32 Apr 17, 2022
c8fea7c
Update user context comments
Shane32 Apr 17, 2022
51ff27b
Update comment for GraphQLHttpMiddlewareOptions
Shane32 Apr 17, 2022
9b2c642
Update api approvals
Shane32 Apr 17, 2022
f2a7011
Update src/Transports.AspNetCore/IUserContextBuilder.cs
Shane32 Apr 17, 2022
6a65c3b
Update src/Transports.AspNetCore/Errors/RequestError.cs
Shane32 May 13, 2022
9398d10
Bump to GraphQL 5.3.0 with necessary changes
Shane32 May 15, 2022
f57505c
Update
Shane32 May 15, 2022
7ce6131
Merge from master
Shane32 May 18, 2022
c673740
Updates
Shane32 May 19, 2022
5671c49
Update
Shane32 May 19, 2022
e1c5ab5
Update src/Transports.AspNetCore/AuthorizationParameters.cs
Shane32 May 19, 2022
27741bf
Update
Shane32 May 19, 2022
b2caa06
Update src/Transports.AspNetCore/GraphQLHttpMiddlewareOptions.cs
Shane32 May 19, 2022
83b0b84
Readonly struct
Shane32 May 19, 2022
20608ec
Merge branch 'update_middleware' of https://github.com/graphql-dotnet…
Shane32 May 19, 2022
0edb99b
Update HandlePost comment
Shane32 May 19, 2022
7ba55ff
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 19, 2022
7a11025
Update src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs
Shane32 May 19, 2022
0f2e282
Update src/Transports.AspNetCore/Errors/WebSocketSubProtocolNotSuppor…
Shane32 May 19, 2022
ed44ade
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
0c84acd
Update NRT
Shane32 May 20, 2022
da68660
Merge branch 'update_middleware' of https://github.com/graphql-dotnet…
Shane32 May 20, 2022
254f738
Remove MediaTypes static class
Shane32 May 20, 2022
77fd8c6
Update
Shane32 May 20, 2022
73c1ac6
Update
Shane32 May 20, 2022
38df925
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
05f8e85
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
ce950c5
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
d9b8529
Invert condition
Shane32 May 20, 2022
c02e17b
Update
Shane32 May 20, 2022
bd14582
Update
Shane32 May 20, 2022
b4c6501
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
b9e3636
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
65a177e
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
60c9fbc
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
0318669
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
d67efe2
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
ef8dbcf
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
e8920ff
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
7557bba
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
dc7354a
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
5f79796
Update src/Transports.AspNetCore/GraphQLHttpMiddleware.cs
Shane32 May 20, 2022
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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>

<PropertyGroup>
<VersionPrefix>6.0.0-preview</VersionPrefix>
<VersionPrefix>7.0.0-preview</VersionPrefix>
<LangVersion>latest</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>logo.64x64.png</PackageIcon>
Expand Down
36 changes: 18 additions & 18 deletions samples/Samples.Server/GraphQLHttpMiddlewareWithLogs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#nullable enable

using System.Diagnostics;
using GraphQL.Server.Transports.AspNetCore;
using GraphQL.Transport;
using GraphQL.Types;

namespace GraphQL.Samples.Server;
Expand All @@ -10,32 +14,28 @@ public class GraphQLHttpMiddlewareWithLogs<TSchema> : GraphQLHttpMiddleware<TSch
private readonly ILogger _logger;

public GraphQLHttpMiddlewareWithLogs(
ILogger<GraphQLHttpMiddleware<TSchema>> logger,
IGraphQLTextSerializer requestDeserializer)
: base(requestDeserializer)
RequestDelegate next,
IGraphQLTextSerializer serializer,
IDocumentExecuter<TSchema> documentExecuter,
IServiceScopeFactory serviceScopeFactory,
GraphQLHttpMiddlewareOptions options,
ILogger<GraphQLHttpMiddleware<TSchema>> logger)
: base(next, serializer, documentExecuter, serviceScopeFactory, options)
{
_logger = logger;
}

protected override Task RequestExecutedAsync(in GraphQLRequestExecutionResult requestExecutionResult)
protected override async Task<ExecutionResult> ExecuteRequestAsync(HttpContext context, GraphQLRequest? request, IServiceProvider serviceProvider, IDictionary<string, object?> userContext)
{
if (requestExecutionResult.Result.Errors != null)
var timer = Stopwatch.StartNew();
var ret = await base.ExecuteRequestAsync(context, request, serviceProvider, userContext);
if (ret.Errors != null)
{
if (requestExecutionResult.IndexInBatch.HasValue)
_logger.LogError("GraphQL execution completed in {Elapsed} with error(s) in batch [{Index}]: {Errors}", requestExecutionResult.Elapsed, requestExecutionResult.IndexInBatch, requestExecutionResult.Result.Errors);
else
_logger.LogError("GraphQL execution completed in {Elapsed} with error(s): {Errors}", requestExecutionResult.Elapsed, requestExecutionResult.Result.Errors);
_logger.LogError("GraphQL execution completed in {Elapsed} with error(s): {Errors}", timer.Elapsed, ret.Errors);
}
else
_logger.LogInformation("GraphQL execution successfully completed in {Elapsed}", requestExecutionResult.Elapsed);

return base.RequestExecutedAsync(requestExecutionResult);
}
_logger.LogInformation("GraphQL execution successfully completed in {Elapsed}", timer.Elapsed);

protected override CancellationToken GetCancellationToken(HttpContext context)
{
// custom CancellationToken example
var cts = CancellationTokenSource.CreateLinkedTokenSource(base.GetCancellationToken(context), new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
return cts.Token;
return ret;
}
}
4 changes: 2 additions & 2 deletions samples/Samples.Server/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using GraphQL.Samples.Schemas.Chat;
using GraphQL.Server;
using GraphQL.Server.Authorization.AspNetCore;
using GraphQL.Server.Transports.AspNetCore;
using GraphQL.Server.Ui.Altair;
using GraphQL.Server.Ui.GraphiQL;
using GraphQL.Server.Ui.Playground;
Expand Down Expand Up @@ -34,7 +35,6 @@ public void ConfigureServices(IServiceCollection services)

services.AddGraphQL(builder => builder
.AddApolloTracing()
.AddHttpMiddleware<ChatSchema, GraphQLHttpMiddlewareWithLogs<ChatSchema>>()
.AddWebSocketsHttpMiddleware<ChatSchema>()
.AddSchema<ChatSchema>()
.ConfigureExecutionOptions(options =>
Expand Down Expand Up @@ -63,7 +63,7 @@ public void Configure(IApplicationBuilder app)
app.UseWebSockets();

app.UseGraphQLWebSockets<ChatSchema>();
app.UseGraphQL<ChatSchema, GraphQLHttpMiddlewareWithLogs<ChatSchema>>();
app.UseGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>("/graphql", new GraphQLHttpMiddlewareOptions());

app.UseGraphQLPlayground(new PlaygroundOptions
{
Expand Down
3 changes: 1 addition & 2 deletions samples/Samples.Server/StartupWithRouting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public void ConfigureServices(IServiceCollection services)

services.AddGraphQL(builder => builder
.AddApolloTracing()
.AddHttpMiddleware<ChatSchema, GraphQLHttpMiddlewareWithLogs<ChatSchema>>()
.AddWebSocketsHttpMiddleware<ChatSchema>()
.AddSchema<ChatSchema>()
.ConfigureExecutionOptions(options =>
Expand Down Expand Up @@ -69,7 +68,7 @@ public void Configure(IApplicationBuilder app)
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQLWebSockets<ChatSchema>();
endpoints.MapGraphQL<ChatSchema, GraphQLHttpMiddlewareWithLogs<ChatSchema>>();
endpoints.MapGraphQL<GraphQLHttpMiddlewareWithLogs<ChatSchema>>();

endpoints.MapGraphQLPlayground(new PlaygroundOptions
{
Expand Down
66 changes: 66 additions & 0 deletions src/Transports.AspNetCore/AuthorizationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#nullable enable

using System.Security.Claims;
using System.Security.Principal;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;

namespace GraphQL.Server.Transports.AspNetCore;

/// <summary>
/// Helper methods for performing connection authorization.
/// </summary>
public static class AuthorizationHelper
{
/// <summary>
/// Performs connection authorization according to the options set within
/// <see cref="AuthorizationParameters{TState}"/>. Returns <see langword="true"/>
/// if authorization was successful or not required.
/// </summary>
public static async ValueTask<bool> AuthorizeAsync<TState>(AuthorizationParameters<TState> options, TState state)
{
if (options.AuthorizationRequired)
{
if (!((options.HttpContext.User ?? NoUser()).Identity ?? NoIdentity()).IsAuthenticated)
{
if (options.OnNotAuthenticated != null)
await options.OnNotAuthenticated(state);
return false;
}
}

if (options.AuthorizedRoles?.Count > 0)
{
var user = options.HttpContext.User ?? NoUser();
foreach (var role in options.AuthorizedRoles)
{
if (user.IsInRole(role))
goto PassRoleCheck;
}
if (options.OnNotAuthorizedRole != null)
await options.OnNotAuthorizedRole(state);
return false;
}
PassRoleCheck:

if (options.AuthorizedPolicy != null)
{
var authorizationService = options.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var authResult = await authorizationService.AuthorizeAsync(options.HttpContext.User ?? NoUser(), null, options.AuthorizedPolicy);
if (!authResult.Succeeded)
{
if (options.OnNotAuthorizedPolicy != null)
await options.OnNotAuthorizedPolicy(state, authResult);
return false;
}
}

return true;
}

private static IIdentity NoIdentity()
=> throw new InvalidOperationException($"IIdentity could not be retrieved from HttpContext.User.Identity.");

private static ClaimsPrincipal NoUser()
=> throw new InvalidOperationException("ClaimsPrincipal could not be retrieved from HttpContext.User.");
}
71 changes: 71 additions & 0 deletions src/Transports.AspNetCore/AuthorizationParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#nullable enable

using System.Security.Claims;
using System.Security.Principal;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;

namespace GraphQL.Server.Transports.AspNetCore;

/// <summary>
/// Authorization parameters.
/// This struct is used to group all necessary parameters together and perform arbitrary
/// actions based on provided authentication properties/attributes/etc.
/// It is not intended to be called from user code.
/// </summary>
public readonly struct AuthorizationParameters<TState>
{
/// <summary>
/// Initializes an instance with a specified <see cref="Microsoft.AspNetCore.Http.HttpContext"/>
/// and parameters copied from the specified instance of <see cref="GraphQLHttpMiddlewareOptions"/>.
/// </summary>
public AuthorizationParameters(
HttpContext httpContext,
GraphQLHttpMiddlewareOptions middlewareOptions,
Func<TState, Task>? onNotAuthenticated,
Func<TState, Task>? onNotAuthorizedRole,
Func<TState, AuthorizationResult, Task>? onNotAuthorizedPolicy)
{
HttpContext = httpContext;
AuthorizationRequired = middlewareOptions.AuthorizationRequired;
AuthorizedRoles = middlewareOptions.AuthorizedRoles;
AuthorizedPolicy = middlewareOptions.AuthorizedPolicy;
OnNotAuthenticated = onNotAuthenticated;
OnNotAuthorizedRole = onNotAuthorizedRole;
OnNotAuthorizedPolicy = onNotAuthorizedPolicy;
}

/// <summary>
/// Gets or sets the <see cref="Microsoft.AspNetCore.Http.HttpContext"/> for the request.
/// </summary>
public HttpContext HttpContext { get; }

/// <inheritdoc cref="GraphQLHttpMiddlewareOptions.AuthorizationRequired"/>
public bool AuthorizationRequired { get; }

/// <inheritdoc cref="GraphQLHttpMiddlewareOptions.AuthorizedRoles"/>
public List<string>? AuthorizedRoles { get; }

/// <inheritdoc cref="GraphQLHttpMiddlewareOptions.AuthorizedPolicy"/>
public string? AuthorizedPolicy { get; }

/// <summary>
/// A delegate which executes if <see cref="AuthorizationRequired"/> is set
/// but <see cref="IIdentity.IsAuthenticated"/> returns <see langword="false"/>.
/// </summary>
public Func<TState, Task>? OnNotAuthenticated { get; }

/// <summary>
/// A delegate which executes if <see cref="AuthorizedRoles"/> is set but
/// <see cref="ClaimsPrincipal.IsInRole(string)"/> returns <see langword="false"/>
/// for all roles.
/// </summary>
public Func<TState, Task>? OnNotAuthorizedRole { get; }

/// <summary>
/// A delegate which executes if <see cref="AuthorizedPolicy"/> is set but
/// <see cref="IAuthorizationService.AuthorizeAsync(ClaimsPrincipal, object, string)"/>
/// returns an unsuccessful <see cref="AuthorizationResult"/> for the specified policy.
/// </summary>
public Func<TState, AuthorizationResult, Task>? OnNotAuthorizedPolicy { get; }
}
38 changes: 38 additions & 0 deletions src/Transports.AspNetCore/Errors/AccessDeniedError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#nullable enable

using GraphQL.Validation;
using GraphQLParser.AST;
using Microsoft.AspNetCore.Authorization;

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
{
/// <inheritdoc cref="AccessDeniedError"/>
public AccessDeniedError(string resource)
: base($"Access denied for {resource}.")
{
}

/// <inheritdoc cref="AccessDeniedError"/>
public AccessDeniedError(string resource, GraphQLParser.ROM originalQuery, params ASTNode[] nodes)
: base(originalQuery, null!, $"Access denied for {resource}.", nodes)
{
}

/// <summary>
/// Returns the policy that would allow access to these node(s).
/// </summary>
public string? PolicyRequired { get; set; }

/// <inheritdoc cref="AuthorizationResult"/>
public AuthorizationResult? PolicyAuthorizationResult { get; set; }

/// <summary>
/// Returns the list of role memberships that would allow access to these node(s).
/// </summary>
public List<string>? RolesRequired { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#nullable enable

using GraphQL.Execution;

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents an error indicating that batched requests are not supported.
/// </summary>
public class BatchedRequestsNotSupportedError : RequestError
{
/// <inheritdoc cref="BatchedRequestsNotSupportedError"/>
public BatchedRequestsNotSupportedError() : base("Batched requests are not supported.") { }
}
19 changes: 19 additions & 0 deletions src/Transports.AspNetCore/Errors/HttpMethodValidationError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#nullable enable

using GraphQL.Validation;
using GraphQLParser.AST;

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents a validation error indicating that the requested operation is not valid
/// for the type of HTTP request.
/// </summary>
public class HttpMethodValidationError : ValidationError
{
/// <inheritdoc cref="HttpMethodValidationError"/>
public HttpMethodValidationError(GraphQLParser.ROM originalQuery, ASTNode node, string message)
: base(originalQuery, null!, message, node)
{
}
}
17 changes: 17 additions & 0 deletions src/Transports.AspNetCore/Errors/InvalidContentTypeError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using GraphQL.Execution;

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
{
/// <inheritdoc cref="InvalidContentTypeError"/>
public InvalidContentTypeError() : base("Invalid 'Content-Type' header.") { }

/// <inheritdoc cref="InvalidContentTypeError"/>
public InvalidContentTypeError(string message) : base("Invalid 'Content-Type' header: " + message) { }
}
17 changes: 17 additions & 0 deletions src/Transports.AspNetCore/Errors/JsonInvalidError.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using GraphQL.Execution;

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents an error indicating that the JSON provided could not be parsed.
/// </summary>
public class JsonInvalidError : RequestError
{
/// <inheritdoc cref="JsonInvalidError"/>
public JsonInvalidError() : base($"JSON body text could not be parsed.") { }

/// <inheritdoc cref="JsonInvalidError"/>
public JsonInvalidError(Exception innerException) : base($"JSON body text could not be parsed. {innerException.Message}") { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable

using GraphQL.Execution;

namespace GraphQL.Server.Transports.AspNetCore.Errors;

/// <summary>
/// Represents an error indicating that none of the requested websocket sub-protocols are supported.
/// </summary>
public class WebSocketSubProtocolNotSupportedError : RequestError
{
/// <inheritdoc cref="WebSocketSubProtocolNotSupportedError"/>
public WebSocketSubProtocolNotSupportedError(IEnumerable<string> requestedSubProtocols)
: base($"Invalid requested WebSocket sub-protocol(s): {string.Join(",", requestedSubProtocols.Select(x => $"'{x}'"))}")
{
}
}
Loading