diff --git a/Directory.Build.props b/Directory.Build.props
index ae8c276..a0bfecf 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -20,7 +20,7 @@
true
enable
false
- [5.3.3,6.0.0)
+ 7.0.0
$(NoWarn);IDE0056;IDE0057
diff --git a/README.md b/README.md
index de83a0f..1217735 100644
--- a/README.md
+++ b/README.md
@@ -33,11 +33,7 @@ for changes from previous versions.
### Typical configuration with HTTP middleware
First add the `GraphQL.AspNetCore3` nuget package to your application. It requires
-`GraphQL` version 5.3.3 or a later 5.x version.
-
-Second, install the `GraphQL.SystemTextJson` or `GraphQL.NewtonsoftJson` package within your
-application if you have not already done so. For best performance, please use the
-`GraphQL.SystemTextJson` package.
+`GraphQL` version 7.0.0 or a later.
Then update your `Program.cs` or `Startup.cs` to register the schema, the serialization engine,
and optionally the HTTP middleware and WebSocket services. Configure WebSockets and GraphQL
@@ -58,8 +54,7 @@ Below is a complete sample of a .NET 6 console app that hosts a GraphQL endpoint
-
-
+
@@ -264,14 +259,14 @@ if you allow anonymous requests.
#### For individual graphs, fields and query arguments
To configure ASP.NET Core authorization for GraphQL, add the corresponding
-validation rule during GraphQL configuration, typically by calling `.AddAuthorization()`
+validation rule during GraphQL configuration, typically by calling `.AddAuthorizationRule()`
as shown below:
```csharp
builder.Services.AddGraphQL(b => b
.AddAutoSchema()
.AddSystemTextJson()
- .AddAuthorization());
+ .AddAuthorizationRule());
```
Both roles and policies are supported for output graph types, fields on output graph types,
@@ -315,7 +310,7 @@ fields are marked with `AllowAnonymous`.
This project does not include user interfaces, such as GraphiQL or Playground,
but you can include references to the ones provided by the [GraphQL Server](https://github.com/graphql-dotnet/server)
-repository which work well with ASP.Net Core 3.1+. Below is a list of the nuget packages offered:
+repository which work well with ASP.Net Core 2.1+. Below is a list of the nuget packages offered:
| Package | Downloads | NuGet Latest |
|------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -408,12 +403,15 @@ and [this](http://www.breachattack.com/#howitworks) for more details.
You may choose to use the .NET Core 2.1 runtime or a .NET Framework runtime.
This library has been tested with .NET Core 2.1 and .NET Framework 4.8.
-The only additional requirement is that you must add this code in your `Startup.cs` file:
+One additional requirement is that you must add this code in your `Startup.cs` file:
```csharp
services.AddHostApplicationLifetime();
```
+You will also need to reference a serializer package such as `GraphQL.NewtonsoftJson`
+or `GraphQL.SystemTextJson`, as `GraphQL.SystemTextJson` is not included in this case.
+
Besides that requirement, all features are supported in exactly the same manner as
when using ASP.NET Core 3.1+. You may find differences in the ASP.NET Core runtime,
such as CORS implementation differences, which are outside the scope of this project.
@@ -513,7 +511,7 @@ and Subscription portions of your schema, as shown below:
builder.Services.AddGraphQL(b => b
.AddSchema()
.AddSystemTextJson()
- .AddAuthorization()); // add authorization validation rule
+ .AddAuthorizationRule()); // add authorization validation rule
var app = builder.Build();
app.UseDeveloperExceptionPage();
diff --git a/migration.md b/migration.md
index 09b29c2..79b56bd 100644
--- a/migration.md
+++ b/migration.md
@@ -1,5 +1,27 @@
# Version history / migration notes
+## 5.0.0
+
+GraphQL.AspNetCore3 v5 requires GraphQL.NET v7 or newer.
+
+`builder.AddAuthorization()` has been renamed to `builder.AddAuthorizationRule()`.
+The old method has been marked as deprecated.
+
+The authorization validation rule and supporting methods have been changed to be
+asynchronous, to match the new asynchronous signatures of `IValidationRule` in
+GraphQL.NET v7. If you override any methods, they will need to be updated with
+the new signature.
+
+The authorization rule now pulls `ClaimsPrincipal` indirectly from
+`ExecutionOptions.User`. This value must be set properly from the ASP.NET middleware.
+While the default implementation has this update in place, if you override
+`GraphQLHttpMiddleware.ExecuteRequestAsync` or do not use the provided ASP.NET
+middleware, you must set the value in your code. Another consequence of this
+change is that the constructor of `AuthorizationValidationRule` does not require
+`IHttpContextAccessor`, and `IHttpContextAccessor` is not required to be registered
+within the dependency injection framework (previously provided automatically by
+`builder.AddAuthorization()`).
+
## 4.0.0
Remove `AllowEmptyQuery` option, as this error condition is now handled by the
diff --git a/src/GraphQL.AspNetCore3/AuthorizationValidationRule.cs b/src/GraphQL.AspNetCore3/AuthorizationValidationRule.cs
index f98f045..d06243f 100644
--- a/src/GraphQL.AspNetCore3/AuthorizationValidationRule.cs
+++ b/src/GraphQL.AspNetCore3/AuthorizationValidationRule.cs
@@ -9,21 +9,11 @@ namespace GraphQL.AspNetCore3;
///
public class AuthorizationValidationRule : IValidationRule
{
- private readonly IHttpContextAccessor _contextAccessor;
-
- ///
- public AuthorizationValidationRule(IHttpContextAccessor httpContextAccessor)
- {
- _contextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
- }
-
///
- public virtual ValueTask ValidateAsync(ValidationContext context)
+ public virtual async ValueTask ValidateAsync(ValidationContext context)
{
- var httpContext = _contextAccessor.HttpContext
- ?? throw new InvalidOperationException("HttpContext could not be retrieved from IHttpContextAccessor.");
- var user = httpContext.User
- ?? throw new InvalidOperationException("ClaimsPrincipal could not be retrieved from HttpContext.");
+ var user = context.User
+ ?? throw new InvalidOperationException("User could not be retrieved from ValidationContext. Please be sure it is set in ExecutionOptions.User.");
var provider = context.RequestServices
?? throw new MissingRequestServicesException();
var authService = provider.GetService()
@@ -31,7 +21,7 @@ public AuthorizationValidationRule(IHttpContextAccessor httpContextAccessor)
var visitor = new AuthorizationVisitor(context, user, authService);
// if the schema fails authentication, report the error and do not perform any additional authorization checks.
- return visitor.ValidateSchema(context) ? new(visitor) : default;
+ return await visitor.ValidateSchemaAsync(context) ? visitor : null;
}
}
diff --git a/src/GraphQL.AspNetCore3/AuthorizationVisitor.cs b/src/GraphQL.AspNetCore3/AuthorizationVisitor.cs
index 5b5e7a3..53b5135 100644
--- a/src/GraphQL.AspNetCore3/AuthorizationVisitor.cs
+++ b/src/GraphQL.AspNetCore3/AuthorizationVisitor.cs
@@ -35,6 +35,6 @@ protected override bool IsInRole(string role)
=> ClaimsPrincipal.IsInRole(role);
///
- protected override AuthorizationResult Authorize(string policy)
- => AuthorizationService.AuthorizeAsync(ClaimsPrincipal, policy).GetAwaiter().GetResult();
+ protected override ValueTask AuthorizeAsync(string policy)
+ => new(AuthorizationService.AuthorizeAsync(ClaimsPrincipal, policy));
}
diff --git a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.Validation.cs b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.Validation.cs
index 5211aa0..d581c30 100644
--- a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.Validation.cs
+++ b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.Validation.cs
@@ -10,14 +10,14 @@ public partial class AuthorizationVisitorBase
/// Validates authorization rules for the schema.
/// Returns a value indicating if validation was successful.
///
- public virtual bool ValidateSchema(ValidationContext context)
- => Validate(context.Schema, null, context);
+ public virtual ValueTask ValidateSchemaAsync(ValidationContext context)
+ => ValidateAsync(context.Schema, null, context);
///
/// Validate a node that is current within the context.
///
- private bool Validate(IProvideMetadata obj, ASTNode? node, ValidationContext context)
- => Validate(BuildValidationInfo(node, obj, context));
+ private ValueTask ValidateAsync(IProvideMetadata obj, ASTNode? node, ValidationContext context)
+ => ValidateAsync(BuildValidationInfo(node, obj, context));
///
/// Initializes a new instance for the specified node.
@@ -67,7 +67,7 @@ public readonly record struct ValidationInfo(
/// as this is handled elsewhere.
/// Returns a value indicating if validation was successful for this node.
///
- protected virtual bool Validate(ValidationInfo info)
+ protected virtual async ValueTask ValidateAsync(ValidationInfo info)
{
bool requiresAuthorization = info.Obj.IsAuthorizationRequired();
if (!requiresAuthorization)
@@ -84,7 +84,7 @@ protected virtual bool Validate(ValidationInfo info)
_policyResults ??= new Dictionary();
foreach (var policy in policies) {
if (!_policyResults.TryGetValue(policy, out var result)) {
- result = Authorize(policy);
+ result = await AuthorizeAsync(policy);
_policyResults.Add(policy, result);
}
if (!result.Succeeded) {
@@ -120,7 +120,7 @@ protected virtual bool Validate(ValidationInfo info)
protected abstract bool IsInRole(string role);
///
- protected abstract AuthorizationResult Authorize(string policy);
+ protected abstract ValueTask AuthorizeAsync(string policy);
///
/// Adds a error to the validation context indicating that the user is not authenticated
diff --git a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs
index 106913b..63c61d8 100644
--- a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs
+++ b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs
@@ -19,7 +19,7 @@ public AuthorizationVisitorBase(ValidationContext context)
private List? _todos;
///
- public virtual void Enter(ASTNode node, ValidationContext context)
+ public virtual async ValueTask EnterAsync(ASTNode node, ValidationContext context)
{
// if the node is the selected operation, or if it is a fragment referenced by the current operation,
// then enable authorization checks on decendant nodes (_checkTree = true)
@@ -58,7 +58,7 @@ public virtual void Enter(ASTNode node, ValidationContext context)
// Fields, unlike types, are validated immediately.
if (!fieldAnonymousAllowed) {
- Validate(field, node, context);
+ await ValidateAsync(field, node, context);
}
}
@@ -92,7 +92,7 @@ public virtual void Enter(ASTNode node, ValidationContext context)
// verify field argument
var arg = context.TypeInfo.GetArgument();
if (arg != null) {
- Validate(arg, node, context);
+ await ValidateAsync(arg, node, context);
}
}
}
@@ -100,7 +100,7 @@ public virtual void Enter(ASTNode node, ValidationContext context)
}
///
- public virtual void Leave(ASTNode node, ValidationContext context)
+ public virtual async ValueTask LeaveAsync(ASTNode node, ValidationContext context)
{
if (!_checkTree) {
// if we are within a field skipped by a directive, resume auth checks at the appropriate time
@@ -114,30 +114,30 @@ public virtual void Leave(ASTNode node, ValidationContext context)
}
if (node == context.Operation) {
_checkTree = false;
- PopAndProcess();
+ await PopAndProcessAsync();
} else if (node is GraphQLFragmentDefinition fragmentDefinition) {
// once a fragment is done being processed, apply it to all types waiting on fragment checks,
// and process checks for types that are not waiting on any fragments
_checkTree = false;
var fragmentName = fragmentDefinition.FragmentName.Name.StringValue;
var ti = _onlyAnonymousSelected.Pop();
- RecursiveResolve(fragmentName, ti, context);
+ await RecursiveResolveAsync(fragmentName, ti, context);
_fragments ??= new();
_fragments.TryAdd(fragmentName, ti);
} else if (_checkTree && node is GraphQLField) {
- PopAndProcess();
+ await PopAndProcessAsync();
}
// pop the current type info, and validate the type if it does not contain only fields marked
// with AllowAnonymous (assuming it is not waiting on fragments)
- void PopAndProcess()
+ async ValueTask PopAndProcessAsync()
{
var info = _onlyAnonymousSelected.Pop();
var type = context.TypeInfo.GetLastType()?.GetNamedType();
if (type == null)
return;
if (info.AnyAuthenticated || (!info.AnyAnonymous && (info.WaitingOnFragments?.Count ?? 0) == 0)) {
- Validate(type, node, context);
+ await ValidateAsync(type, node, context);
} else if (info.WaitingOnFragments?.Count > 0) {
_todos ??= new();
_todos.Add(new(BuildValidationInfo(node, type, context), info));
@@ -205,7 +205,7 @@ static bool GetDirectiveValue(GraphQLDirective directive, ValidationContext cont
/// Runs when a fragment is added or updated; the fragment might not be waiting on any
/// other fragments, or it still might be.
///
- private void RecursiveResolve(string fragmentName, TypeInfo ti, ValidationContext context)
+ private async ValueTask RecursiveResolveAsync(string fragmentName, TypeInfo ti, ValidationContext context)
{
// first see if any other fragments are waiting on this fragment
if (_fragments != null) {
@@ -216,7 +216,7 @@ private void RecursiveResolve(string fragmentName, TypeInfo ti, ValidationContex
ti2.AnyAuthenticated |= ti.AnyAuthenticated;
ti2.AnyAnonymous |= ti.AnyAnonymous;
_fragments[fragment.Key] = ti2;
- RecursiveResolve(fragment.Key, ti2, context);
+ await RecursiveResolveAsync(fragment.Key, ti2, context);
goto Retry; // modifying a collection at runtime is not supported
}
}
@@ -234,7 +234,7 @@ private void RecursiveResolve(string fragmentName, TypeInfo ti, ValidationContex
_todos.RemoveAt(i);
count--;
if (todo.AnyAuthenticated || !todo.AnyAnonymous) {
- Validate(todo.ValidationInfo);
+ await ValidateAsync(todo.ValidationInfo);
}
}
}
diff --git a/src/GraphQL.AspNetCore3/GraphQL.AspNetCore3.csproj b/src/GraphQL.AspNetCore3/GraphQL.AspNetCore3.csproj
index 0f5ffd8..00bc212 100644
--- a/src/GraphQL.AspNetCore3/GraphQL.AspNetCore3.csproj
+++ b/src/GraphQL.AspNetCore3/GraphQL.AspNetCore3.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/GraphQL.AspNetCore3/GraphQLBuilderExtensions.cs b/src/GraphQL.AspNetCore3/GraphQLBuilderExtensions.cs
index 6d7fa99..05c37e0 100644
--- a/src/GraphQL.AspNetCore3/GraphQLBuilderExtensions.cs
+++ b/src/GraphQL.AspNetCore3/GraphQLBuilderExtensions.cs
@@ -11,10 +11,17 @@ public static class GraphQLBuilderExtensions
/// Registers with the dependency injection framework
/// and configures it to be used when executing a request.
///
+ [Obsolete("Please use AddAuthorizationRule")]
public static IGraphQLBuilder AddAuthorization(this IGraphQLBuilder builder)
+ => AddAuthorizationRule(builder);
+
+ ///
+ /// Registers with the dependency injection framework
+ /// and configures it to be used when executing a request.
+ ///
+ public static IGraphQLBuilder AddAuthorizationRule(this IGraphQLBuilder builder)
{
builder.AddValidationRule(true);
- builder.Services.TryRegister(ServiceLifetime.Singleton);
return builder;
}
diff --git a/src/GraphQL.AspNetCore3/GraphQLHttpMiddleware.cs b/src/GraphQL.AspNetCore3/GraphQLHttpMiddleware.cs
index a0ad71b..136d190 100644
--- a/src/GraphQL.AspNetCore3/GraphQLHttpMiddleware.cs
+++ b/src/GraphQL.AspNetCore3/GraphQLHttpMiddleware.cs
@@ -354,6 +354,7 @@ protected virtual async Task ExecuteRequestAsync(HttpContext co
OperationName = request?.OperationName,
RequestServices = serviceProvider,
UserContext = userContext,
+ User = context.User,
};
if (!context.WebSockets.IsWebSocketRequest) {
if (HttpMethods.IsGet(context.Request.Method)) {
diff --git a/src/GraphQL.AspNetCore3/WebSockets/GraphQLWs/SubscriptionServer.cs b/src/GraphQL.AspNetCore3/WebSockets/GraphQLWs/SubscriptionServer.cs
index 0e3dd4d..8e75172 100644
--- a/src/GraphQL.AspNetCore3/WebSockets/GraphQLWs/SubscriptionServer.cs
+++ b/src/GraphQL.AspNetCore3/WebSockets/GraphQLWs/SubscriptionServer.cs
@@ -196,6 +196,7 @@ protected override async Task ExecuteRequestAsync(OperationMess
OperationName = request.OperationName,
RequestServices = scope.ServiceProvider,
CancellationToken = CancellationToken,
+ User = Connection.HttpContext.User,
};
if (UserContext != null)
options.UserContext = UserContext;
diff --git a/src/GraphQL.AspNetCore3/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs b/src/GraphQL.AspNetCore3/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs
index bbdd9ac..4d41a11 100644
--- a/src/GraphQL.AspNetCore3/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs
+++ b/src/GraphQL.AspNetCore3/WebSockets/SubscriptionsTransportWs/SubscriptionServer.cs
@@ -176,6 +176,7 @@ protected override async Task ExecuteRequestAsync(OperationMess
OperationName = request.OperationName,
RequestServices = scope.ServiceProvider,
CancellationToken = CancellationToken,
+ User = Connection.HttpContext.User,
};
if (UserContext != null)
options.UserContext = UserContext;
diff --git a/src/Samples/AuthorizationSample/AuthorizationSample.csproj b/src/Samples/AuthorizationSample/AuthorizationSample.csproj
index d23c5c7..ce77539 100644
--- a/src/Samples/AuthorizationSample/AuthorizationSample.csproj
+++ b/src/Samples/AuthorizationSample/AuthorizationSample.csproj
@@ -18,8 +18,7 @@
-
-
+
diff --git a/src/Samples/AuthorizationSample/Program.cs b/src/Samples/AuthorizationSample/Program.cs
index e367c1d..e1c85e9 100644
--- a/src/Samples/AuthorizationSample/Program.cs
+++ b/src/Samples/AuthorizationSample/Program.cs
@@ -33,7 +33,7 @@
builder.Services.AddGraphQL(b => b
.AddAutoSchema(s => s.WithMutation())
.AddSystemTextJson()
- .AddAuthorization());
+ .AddAuthorizationRule());
// ------------------------------------
var app = builder.Build();
@@ -61,11 +61,11 @@
app.UseGraphQL("/graphql");
// configure Playground at "/ui/graphql"
app.UseGraphQLPlayground(
+ "/ui/graphql",
new GraphQL.Server.Ui.Playground.PlaygroundOptions {
GraphQLEndPoint = new PathString("/graphql"),
SubscriptionsEndPoint = new PathString("/graphql"),
- },
- "/ui/graphql");
+ });
// -------------------------------------
app.MapRazorPages();
diff --git a/src/Samples/BasicSample/BasicSample.csproj b/src/Samples/BasicSample/BasicSample.csproj
index 9630206..9eaa32f 100644
--- a/src/Samples/BasicSample/BasicSample.csproj
+++ b/src/Samples/BasicSample/BasicSample.csproj
@@ -10,8 +10,7 @@
-
-
+
diff --git a/src/Samples/BasicSample/Program.cs b/src/Samples/BasicSample/Program.cs
index 2ee1d64..e21b193 100644
--- a/src/Samples/BasicSample/Program.cs
+++ b/src/Samples/BasicSample/Program.cs
@@ -19,10 +19,10 @@
app.UseGraphQL("/graphql");
// configure Playground at "/"
app.UseGraphQLPlayground(
+ "/",
new GraphQL.Server.Ui.Playground.PlaygroundOptions {
GraphQLEndPoint = new PathString("/graphql"),
SubscriptionsEndPoint = new PathString("/graphql"),
- },
- "/");
+ });
await app.RunAsync();
diff --git a/src/Samples/ControllerSample/ControllerSample.csproj b/src/Samples/ControllerSample/ControllerSample.csproj
index 5adeefc..e480696 100644
--- a/src/Samples/ControllerSample/ControllerSample.csproj
+++ b/src/Samples/ControllerSample/ControllerSample.csproj
@@ -10,7 +10,6 @@
-
diff --git a/src/Samples/ControllerSample/Controllers/HomeController.cs b/src/Samples/ControllerSample/Controllers/HomeController.cs
index 522a3bf..4cd5ca8 100644
--- a/src/Samples/ControllerSample/Controllers/HomeController.cs
+++ b/src/Samples/ControllerSample/Controllers/HomeController.cs
@@ -67,6 +67,7 @@ private async Task ExecuteGraphQLRequestAsync(GraphQLRequest? req
Extensions = request?.Extensions,
CancellationToken = HttpContext.RequestAborted,
RequestServices = HttpContext.RequestServices,
+ User = HttpContext.User,
};
IValidationRule rule = HttpMethods.IsGet(HttpContext.Request.Method) ? new HttpGetValidationRule() : new HttpPostValidationRule();
opts.ValidationRules = DocumentValidator.CoreRules.Append(rule);
diff --git a/src/Samples/CorsSample/CorsSample.csproj b/src/Samples/CorsSample/CorsSample.csproj
index 9630206..9eaa32f 100644
--- a/src/Samples/CorsSample/CorsSample.csproj
+++ b/src/Samples/CorsSample/CorsSample.csproj
@@ -10,8 +10,7 @@
-
-
+
diff --git a/src/Samples/EndpointRoutingSample/EndpointRoutingSample.csproj b/src/Samples/EndpointRoutingSample/EndpointRoutingSample.csproj
index 9630206..9eaa32f 100644
--- a/src/Samples/EndpointRoutingSample/EndpointRoutingSample.csproj
+++ b/src/Samples/EndpointRoutingSample/EndpointRoutingSample.csproj
@@ -10,8 +10,7 @@
-
-
+
diff --git a/src/Samples/MultipleSchema/MultipleSchema.csproj b/src/Samples/MultipleSchema/MultipleSchema.csproj
index e70e67e..51b5341 100644
--- a/src/Samples/MultipleSchema/MultipleSchema.csproj
+++ b/src/Samples/MultipleSchema/MultipleSchema.csproj
@@ -9,8 +9,7 @@
-
-
+
diff --git a/src/Samples/MultipleSchema/Program.cs b/src/Samples/MultipleSchema/Program.cs
index 3b758a8..e4a5efe 100644
--- a/src/Samples/MultipleSchema/Program.cs
+++ b/src/Samples/MultipleSchema/Program.cs
@@ -22,17 +22,17 @@
app.UseGraphQL("/dogs/graphql");
// configure Playground at "/cats"
app.UseGraphQLPlayground(
+ "/cats",
new GraphQL.Server.Ui.Playground.PlaygroundOptions {
GraphQLEndPoint = new PathString("/cats/graphql"),
SubscriptionsEndPoint = new PathString("/cats/graphql"),
- },
- "/cats");
+ });
// configure Playground at "/dogs"
app.UseGraphQLPlayground(
+ "/dogs",
new GraphQL.Server.Ui.Playground.PlaygroundOptions {
GraphQLEndPoint = new PathString("/dogs/graphql"),
SubscriptionsEndPoint = new PathString("/dogs/graphql"),
- },
- "/dogs");
+ });
app.MapRazorPages();
await app.RunAsync();
diff --git a/src/Samples/Net48Sample/Net48Sample.csproj b/src/Samples/Net48Sample/Net48Sample.csproj
index f788427..1fa74d8 100644
--- a/src/Samples/Net48Sample/Net48Sample.csproj
+++ b/src/Samples/Net48Sample/Net48Sample.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Samples/PagesSample/PagesSample.csproj b/src/Samples/PagesSample/PagesSample.csproj
index 5adeefc..e480696 100644
--- a/src/Samples/PagesSample/PagesSample.csproj
+++ b/src/Samples/PagesSample/PagesSample.csproj
@@ -10,7 +10,6 @@
-
diff --git a/src/Tests.ApiApprovals/GraphQL.AspNetCore3.approved.txt b/src/Tests.ApiApprovals/GraphQL.AspNetCore3.approved.txt
index d1b8e88..9fc1437 100644
--- a/src/Tests.ApiApprovals/GraphQL.AspNetCore3.approved.txt
+++ b/src/Tests.ApiApprovals/GraphQL.AspNetCore3.approved.txt
@@ -17,7 +17,7 @@ namespace GraphQL.AspNetCore3
}
public class AuthorizationValidationRule : GraphQL.Validation.IValidationRule
{
- public AuthorizationValidationRule(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor) { }
+ public AuthorizationValidationRule() { }
public virtual System.Threading.Tasks.ValueTask ValidateAsync(GraphQL.Validation.ValidationContext context) { }
}
public class AuthorizationVisitor : GraphQL.AspNetCore3.AuthorizationVisitorBase
@@ -26,25 +26,25 @@ namespace GraphQL.AspNetCore3
protected Microsoft.AspNetCore.Authorization.IAuthorizationService AuthorizationService { get; }
protected System.Security.Claims.ClaimsPrincipal ClaimsPrincipal { get; }
protected override bool IsAuthenticated { get; }
- protected override Microsoft.AspNetCore.Authorization.AuthorizationResult Authorize(string policy) { }
+ protected override System.Threading.Tasks.ValueTask AuthorizeAsync(string policy) { }
protected override bool IsInRole(string role) { }
}
public abstract class AuthorizationVisitorBase : GraphQL.Validation.INodeVisitor
{
public AuthorizationVisitorBase(GraphQL.Validation.ValidationContext context) { }
protected abstract bool IsAuthenticated { get; }
- protected abstract Microsoft.AspNetCore.Authorization.AuthorizationResult Authorize(string policy);
- public virtual void Enter(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { }
+ protected abstract System.Threading.Tasks.ValueTask AuthorizeAsync(string policy);
+ public virtual System.Threading.Tasks.ValueTask EnterAsync(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { }
protected virtual string GenerateResourceDescription(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info) { }
protected System.Collections.Generic.List? GetRecursivelyReferencedFragments(GraphQL.Validation.ValidationContext validationContext) { }
protected virtual void HandleNodeNotAuthorized(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info) { }
protected virtual void HandleNodeNotInPolicy(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info, string policy, Microsoft.AspNetCore.Authorization.AuthorizationResult authorizationResult) { }
protected virtual void HandleNodeNotInRoles(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info, System.Collections.Generic.List roles) { }
protected abstract bool IsInRole(string role);
- public virtual void Leave(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { }
+ public virtual System.Threading.Tasks.ValueTask LeaveAsync(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { }
protected virtual bool SkipNode(GraphQLParser.AST.ASTNode node, GraphQL.Validation.ValidationContext context) { }
- protected virtual bool Validate(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info) { }
- public virtual bool ValidateSchema(GraphQL.Validation.ValidationContext context) { }
+ protected virtual System.Threading.Tasks.ValueTask ValidateAsync(GraphQL.AspNetCore3.AuthorizationVisitorBase.ValidationInfo info) { }
+ public virtual System.Threading.Tasks.ValueTask ValidateSchemaAsync(GraphQL.Validation.ValidationContext context) { }
public readonly struct ValidationInfo : System.IEquatable
{
public ValidationInfo(GraphQL.Types.IProvideMetadata Obj, GraphQLParser.AST.ASTNode? Node, GraphQL.Types.IFieldType? ParentFieldType, GraphQL.Types.IGraphType? ParentGraphType, GraphQL.Validation.ValidationContext Context) { }
@@ -62,7 +62,9 @@ namespace GraphQL.AspNetCore3
}
public static class GraphQLBuilderExtensions
{
+ [System.Obsolete("Please use AddAuthorizationRule")]
public static GraphQL.DI.IGraphQLBuilder AddAuthorization(this GraphQL.DI.IGraphQLBuilder builder) { }
+ public static GraphQL.DI.IGraphQLBuilder AddAuthorizationRule(this GraphQL.DI.IGraphQLBuilder builder) { }
public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder(this GraphQL.DI.IGraphQLBuilder builder, GraphQL.DI.ServiceLifetime serviceLifetime = 0)
where TUserContextBuilder : class, GraphQL.AspNetCore3.IUserContextBuilder { }
public static GraphQL.DI.IGraphQLBuilder AddUserContextBuilder(this GraphQL.DI.IGraphQLBuilder builder, System.Func> creator)
diff --git a/src/Tests/AuthorizationTests.cs b/src/Tests/AuthorizationTests.cs
index fcb59df..c6cad3f 100644
--- a/src/Tests/AuthorizationTests.cs
+++ b/src/Tests/AuthorizationTests.cs
@@ -46,10 +46,6 @@ private IValidationResult Validate(string query, bool shouldPassCoreRules = true
});
var mockServices = new Mock(MockBehavior.Strict);
mockServices.Setup(x => x.GetService(typeof(IAuthorizationService))).Returns(mockAuthorizationService.Object);
- var mockHttpContext = new Mock(MockBehavior.Strict);
- mockHttpContext.Setup(x => x.User).Returns(_principal);
- var mockContextAccessor = new Mock(MockBehavior.Strict);
- mockContextAccessor.Setup(x => x.HttpContext).Returns(mockHttpContext.Object);
var document = GraphQLParser.Parser.Parse(query);
var inputs = new GraphQLSerializer().Deserialize(variables) ?? Inputs.Empty;
@@ -63,6 +59,7 @@ private IValidationResult Validate(string query, bool shouldPassCoreRules = true
UserContext = new Dictionary(),
Variables = inputs,
RequestServices = mockServices.Object,
+ User = _principal,
}).GetAwaiter().GetResult(); // there is no async code being tested
coreRulesResult.IsValid.ShouldBe(shouldPassCoreRules);
@@ -70,11 +67,12 @@ private IValidationResult Validate(string query, bool shouldPassCoreRules = true
Document = document,
Extensions = Inputs.Empty,
Operation = (GraphQLOperationDefinition)document.Definitions.First(x => x.Kind == ASTNodeKind.OperationDefinition),
- Rules = new IValidationRule[] { new AuthorizationValidationRule(mockContextAccessor.Object) },
+ Rules = new IValidationRule[] { new AuthorizationValidationRule() },
Schema = _schema,
UserContext = new Dictionary(),
Variables = inputs,
RequestServices = mockServices.Object,
+ User = _principal,
}).GetAwaiter().GetResult(); // there is no async code being tested
return result;
}
@@ -533,18 +531,16 @@ private void Apply(IProvideMetadata obj, Mode mode)
[Fact]
public void Constructors()
{
- Should.Throw(() => new AuthorizationValidationRule(null!));
Should.Throw(() => new AuthorizationVisitor(null!, _principal, Mock.Of()));
Should.Throw(() => new AuthorizationVisitor(new ValidationContext(), null!, Mock.Of()));
Should.Throw(() => new AuthorizationVisitor(new ValidationContext(), _principal, null!));
}
[Theory]
- [InlineData(true, false, false, false)]
- [InlineData(false, true, false, false)]
- [InlineData(false, false, true, false)]
- [InlineData(false, false, false, true)]
- public void MiscErrors(bool noHttpContext, bool noClaimsPrincipal, bool noRequestServices, bool noAuthenticationService)
+ [InlineData(true, false, false)]
+ [InlineData(false, true, false)]
+ [InlineData(false, false, true)]
+ public void MiscErrors(bool noClaimsPrincipal, bool noRequestServices, bool noAuthenticationService)
{
var mockAuthorizationService = new Mock(MockBehavior.Strict);
mockAuthorizationService.Setup(x => x.AuthorizeAsync(_principal, null, It.IsAny())).Returns((_, _, policy) => {
@@ -554,10 +550,6 @@ public void MiscErrors(bool noHttpContext, bool noClaimsPrincipal, bool noReques
});
var mockServices = new Mock(MockBehavior.Strict);
mockServices.Setup(x => x.GetService(typeof(IAuthorizationService))).Returns(noAuthenticationService ? null! : mockAuthorizationService.Object);
- var mockHttpContext = new Mock(MockBehavior.Strict);
- mockHttpContext.Setup(x => x.User).Returns(noClaimsPrincipal ? null! : _principal);
- var mockContextAccessor = new Mock(MockBehavior.Strict);
- mockContextAccessor.Setup(x => x.HttpContext).Returns(noHttpContext ? null! : mockHttpContext.Object);
var document = GraphQLParser.Parser.Parse("{ __typename }");
var validator = new DocumentValidator();
@@ -565,18 +557,16 @@ public void MiscErrors(bool noHttpContext, bool noClaimsPrincipal, bool noReques
Document = document,
Extensions = Inputs.Empty,
Operation = (GraphQLOperationDefinition)document.Definitions.Single(x => x.Kind == ASTNodeKind.OperationDefinition),
- Rules = new IValidationRule[] { new AuthorizationValidationRule(mockContextAccessor.Object) },
+ Rules = new IValidationRule[] { new AuthorizationValidationRule() },
Schema = _schema,
UserContext = new Dictionary(),
Variables = Inputs.Empty,
RequestServices = noRequestServices ? null : mockServices.Object,
+ User = noClaimsPrincipal ? null : _principal,
}).GetAwaiter().GetResult()); // there is no async code being tested
- if (noHttpContext)
- err.ShouldBeOfType().Message.ShouldBe("HttpContext could not be retrieved from IHttpContextAccessor.");
-
if (noClaimsPrincipal)
- err.ShouldBeOfType().Message.ShouldBe("ClaimsPrincipal could not be retrieved from HttpContext.");
+ err.ShouldBeOfType().Message.ShouldBe("User could not be retrieved from ValidationContext. Please be sure it is set in ExecutionOptions.User.");
if (noRequestServices)
err.ShouldBeOfType();
@@ -591,10 +581,6 @@ public void NullIdentity()
var mockAuthorizationService = new Mock(MockBehavior.Strict);
var mockServices = new Mock(MockBehavior.Strict);
mockServices.Setup(x => x.GetService(typeof(IAuthorizationService))).Returns(mockAuthorizationService.Object);
- var mockHttpContext = new Mock(MockBehavior.Strict);
- mockHttpContext.Setup(x => x.User).Returns(new ClaimsPrincipal());
- var mockContextAccessor = new Mock(MockBehavior.Strict);
- mockContextAccessor.Setup(x => x.HttpContext).Returns(mockHttpContext.Object);
var document = GraphQLParser.Parser.Parse("{ __typename }");
var validator = new DocumentValidator();
_schema.Authorize();
@@ -603,11 +589,12 @@ public void NullIdentity()
Document = document,
Extensions = Inputs.Empty,
Operation = (GraphQLOperationDefinition)document.Definitions.Single(x => x.Kind == ASTNodeKind.OperationDefinition),
- Rules = new IValidationRule[] { new AuthorizationValidationRule(mockContextAccessor.Object) },
+ Rules = new IValidationRule[] { new AuthorizationValidationRule() },
Schema = _schema,
UserContext = new Dictionary(),
Variables = Inputs.Empty,
RequestServices = mockServices.Object,
+ User = new ClaimsPrincipal(),
}).GetAwaiter().GetResult(); // there is no async code being tested
result.Errors.ShouldHaveSingleItem().ShouldBeOfType().Message.ShouldBe("Access denied for schema.");
@@ -700,24 +687,17 @@ public async Task TestPipeline(bool authenticated)
services.AddGraphQL(b => b
.AddSchema(_schema)
.AddSystemTextJson()
- .AddAuthorization());
+ .AddAuthorizationRule());
services.AddSingleton(Mock.Of(MockBehavior.Strict));
- var mockContext = new Mock(MockBehavior.Strict);
- mockContext.Setup(x => x.User).Returns(_principal);
-
- var mockContextAccessor = new Mock(MockBehavior.Strict);
- mockContextAccessor.Setup(x => x.HttpContext).Returns(mockContext.Object);
-
- services.AddSingleton(mockContextAccessor.Object);
-
using var provider = services.BuildServiceProvider();
var executer = provider.GetRequiredService>();
var ret = await executer.ExecuteAsync(new ExecutionOptions {
Query = @"{ parent { child } }",
RequestServices = provider,
+ User = _principal,
});
var serializer = provider.GetRequiredService();
@@ -729,6 +709,51 @@ public async Task TestPipeline(bool authenticated)
actual.ShouldBe(@"{""errors"":[{""message"":""Access denied for field \u0027parent\u0027 on type \u0027QueryType\u0027."",""locations"":[{""line"":1,""column"":3}],""extensions"":{""code"":""ACCESS_DENIED"",""codes"":[""ACCESS_DENIED""]}}]}");
}
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task EndToEnd(bool authenticated)
+ {
+ _field.Authorize();
+
+ if (authenticated)
+ SetAuthorized();
+
+ var hostBuilder = new WebHostBuilder();
+ hostBuilder.ConfigureServices(services => {
+ services.AddSingleton();
+ services.AddGraphQL(b => b
+ .AddSchema(_schema)
+ .AddSystemTextJson()
+ .AddAuthorizationRule());
+ services.AddAuthentication();
+ services.AddAuthorization();
+#if NETCOREAPP2_1 || NET48
+ services.AddHostApplicationLifetime();
+#endif
+ });
+ hostBuilder.Configure(app => {
+ app.UseWebSockets();
+ // simulate app.UseAuthentication()
+ app.Use(next => context => {
+ context.User = _principal;
+ return next(context);
+ });
+ app.UseGraphQL();
+ });
+ using var server = new TestServer(hostBuilder);
+
+ using var client = server.CreateClient();
+ using var response = await client.GetAsync("/graphql?query={ parent { child } }");
+ response.StatusCode.ShouldBe(authenticated ? System.Net.HttpStatusCode.OK : System.Net.HttpStatusCode.BadRequest);
+ var actual = await response.Content.ReadAsStringAsync();
+
+ if (authenticated)
+ actual.ShouldBe(@"{""data"":{""parent"":null}}");
+ else
+ actual.ShouldBe(@"{""errors"":[{""message"":""Access denied for field \u0027parent\u0027 on type \u0027QueryType\u0027."",""locations"":[{""line"":1,""column"":3}],""extensions"":{""code"":""ACCESS_DENIED"",""codes"":[""ACCESS_DENIED""]}}]}");
+ }
+
public enum Mode
{
None,
diff --git a/src/Tests/Middleware/FileUploadTests.cs b/src/Tests/Middleware/FileUploadTests.cs
index 4b9e349..ebafdda 100644
--- a/src/Tests/Middleware/FileUploadTests.cs
+++ b/src/Tests/Middleware/FileUploadTests.cs
@@ -108,13 +108,10 @@ public MySchema()
var query = new ObjectGraphType {
Name = "Query",
};
- query.Field(
- "ConvertToBase64",
- arguments: new QueryArguments(
- new QueryArgument(typeof(StringGraphType)) { Name = "prefix" },
- new QueryArgument(typeof(NonNullGraphType)) { Name = "file" }
- ),
- resolve: context => {
+ query.Field("ConvertToBase64")
+ .Argument("prefix")
+ .Argument>("file")
+ .Resolve(context => {
var prefix = context.GetArgument("prefix");
var file = context.GetArgument("file");
var memStream = new MemoryStream();
diff --git a/src/Tests/WebSockets/NewSubscriptionServerTests.cs b/src/Tests/WebSockets/NewSubscriptionServerTests.cs
index dec5987..6ec1c3f 100644
--- a/src/Tests/WebSockets/NewSubscriptionServerTests.cs
+++ b/src/Tests/WebSockets/NewSubscriptionServerTests.cs
@@ -1,3 +1,5 @@
+using System.Security.Claims;
+
namespace Tests.WebSockets;
public class NewSubscriptionServerTests : IDisposable
@@ -350,6 +352,10 @@ public async Task ExecuteRequestAsync()
.Verifiable();
mockScope.Setup(x => x.Dispose()).Verifiable();
var result = Mock.Of(MockBehavior.Strict);
+ var principal = new ClaimsPrincipal();
+ var mockContext = new Mock(MockBehavior.Strict);
+ mockContext.Setup(x => x.User).Returns(principal).Verifiable();
+ _mockStream.Setup(x => x.HttpContext).Returns(mockContext.Object).Verifiable();
var mockUserContext = new Mock>(MockBehavior.Strict);
_server.Set_UserContext(mockUserContext.Object);
_mockDocumentExecuter.Setup(x => x.ExecuteAsync(It.IsAny()))
@@ -361,11 +367,13 @@ public async Task ExecuteRequestAsync()
options.OperationName.ShouldBe(request.OperationName);
options.UserContext.ShouldBe(mockUserContext.Object);
options.RequestServices.ShouldBe(mockServiceProvider.Object);
+ options.User.ShouldBe(principal);
return Task.FromResult(result);
})
.Verifiable();
var actual = await _server.Do_ExecuteRequestAsync(message);
actual.ShouldBe(result);
+ mockContext.Verify();
_mockDocumentExecuter.Verify();
_mockSerializer.Verify();
_mockServiceScopeFactory.Verify();
diff --git a/src/Tests/WebSockets/OldSubscriptionServerTests.cs b/src/Tests/WebSockets/OldSubscriptionServerTests.cs
index 42c02ff..9b35894 100644
--- a/src/Tests/WebSockets/OldSubscriptionServerTests.cs
+++ b/src/Tests/WebSockets/OldSubscriptionServerTests.cs
@@ -1,3 +1,5 @@
+using System.Security.Claims;
+
namespace Tests.WebSockets;
public class OldSubscriptionServerTests : IDisposable
@@ -305,6 +307,10 @@ public async Task ExecuteRequestAsync()
.Verifiable();
mockScope.Setup(x => x.Dispose()).Verifiable();
var result = Mock.Of(MockBehavior.Strict);
+ var principal = new ClaimsPrincipal();
+ var mockContext = new Mock(MockBehavior.Strict);
+ mockContext.Setup(x => x.User).Returns(principal).Verifiable();
+ _mockStream.Setup(x => x.HttpContext).Returns(mockContext.Object).Verifiable();
var mockUserContext = new Mock>(MockBehavior.Strict);
_server.Set_UserContext(mockUserContext.Object);
_mockDocumentExecuter.Setup(x => x.ExecuteAsync(It.IsAny()))
@@ -316,11 +322,13 @@ public async Task ExecuteRequestAsync()
options.OperationName.ShouldBe(request.OperationName);
options.UserContext.ShouldBe(mockUserContext.Object);
options.RequestServices.ShouldBe(mockServiceProvider.Object);
+ options.User.ShouldBe(principal);
return Task.FromResult(result);
})
.Verifiable();
var actual = await _server.Do_ExecuteRequestAsync(message);
actual.ShouldBe(result);
+ mockContext.Verify();
_mockDocumentExecuter.Verify();
_mockSerializer.Verify();
_mockServiceScopeFactory.Verify();