Skip to content

Commit 09e9278

Browse files
authored
Authorize only actual operation + Added authorization for VariableReference fields (#676)
1 parent 46e5fa4 commit 09e9278

File tree

15 files changed

+123
-27
lines changed

15 files changed

+123
-27
lines changed

samples/Samples.Server/Startup.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using GraphQL.DataLoader;
33
using GraphQL.Execution;
4+
using System.Threading.Tasks;
45
using GraphQL.Samples.Schemas.Chat;
56
using GraphQL.Server;
67
using GraphQL.Server.Ui.Altair;
@@ -36,11 +37,15 @@ public void ConfigureServices(IServiceCollection services)
3637
MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services)
3738
.AddServer(true)
3839
.AddSchema<ChatSchema>()
39-
.ConfigureExecution(options =>
40+
.ConfigureExecutionOptions(options =>
4041
{
4142
options.EnableMetrics = Environment.IsDevelopment();
4243
var logger = options.RequestServices.GetRequiredService<ILogger<Startup>>();
43-
options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message);
44+
options.UnhandledExceptionDelegate = ctx =>
45+
{
46+
logger.LogError("{Error} occurred", ctx.OriginalException.Message);
47+
return Task.CompletedTask;
48+
};
4449
})
4550
.AddSystemTextJson()
4651
.AddErrorInfoProvider<CustomErrorInfoProvider>()

samples/Samples.Server/StartupWithRouting.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using GraphQL.DataLoader;
33
using GraphQL.Execution;
4+
using System.Threading.Tasks;
45
using GraphQL.Samples.Schemas.Chat;
56
using GraphQL.Server;
67
using GraphQL.Server.Ui.Altair;
@@ -37,11 +38,15 @@ public void ConfigureServices(IServiceCollection services)
3738
MicrosoftDI.GraphQLBuilderExtensions.AddGraphQL(services)
3839
.AddServer(true)
3940
.AddSchema<ChatSchema>()
40-
.ConfigureExecution(options =>
41+
.ConfigureExecutionOptions(options =>
4142
{
4243
options.EnableMetrics = Environment.IsDevelopment();
4344
var logger = options.RequestServices.GetRequiredService<ILogger<Startup>>();
44-
options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message);
45+
options.UnhandledExceptionDelegate = ctx =>
46+
{
47+
logger.LogError("{Error} occurred", ctx.OriginalException.Message);
48+
return Task.CompletedTask;
49+
};
4550
})
4651
.AddDefaultEndpointSelectorPolicy()
4752
.AddSystemTextJson()

src/All/All.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
<ProjectReference Include="..\Ui.Playground\Ui.Playground.csproj" />
2222
<ProjectReference Include="..\Ui.Voyager\Ui.Voyager.csproj" />
2323

24-
<PackageReference Include="GraphQL.MemoryCache" Version="4.6.1" />
25-
<PackageReference Include="GraphQL.MicrosoftDI" Version="4.6.1" />
26-
<PackageReference Include="GraphQL.SystemReactive" Version="4.6.1" />
24+
<PackageReference Include="GraphQL.MemoryCache" Version="5.0.0-preview-397" />
25+
<PackageReference Include="GraphQL.MicrosoftDI" Version="5.0.0-preview-397" />
26+
<PackageReference Include="GraphQL.SystemReactive" Version="5.0.0-preview-397" />
2727
</ItemGroup>
2828

2929
</Project>

src/Authorization.AspNetCore/AuthorizationValidationRule.cs

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Security.Claims;
34
using System.Threading.Tasks;
45
using GraphQL.Language.AST;
@@ -28,45 +29,129 @@ public AuthorizationValidationRule(IAuthorizationService authorizationService, I
2829
_claimsPrincipalAccessor = claimsPrincipalAccessor;
2930
}
3031

32+
private bool ShouldBeSkipped(Operation actualOperation, ValidationContext context)
33+
{
34+
if (context.Document.Operations.Count <= 1)
35+
{
36+
return false;
37+
}
38+
39+
int i = 0;
40+
do
41+
{
42+
var ancestor = context.TypeInfo.GetAncestor(i++);
43+
44+
if (ancestor == actualOperation)
45+
{
46+
return false;
47+
}
48+
49+
if (ancestor == context.Document)
50+
{
51+
return true;
52+
}
53+
54+
if (ancestor is FragmentDefinition fragment)
55+
{
56+
return !FragmentBelongsToOperation(fragment, actualOperation);
57+
}
58+
} while (true);
59+
}
60+
61+
private bool FragmentBelongsToOperation(FragmentDefinition fragment, Operation operation)
62+
{
63+
bool belongs = false;
64+
void Visit(INode node, int _)
65+
{
66+
if (belongs)
67+
{
68+
return;
69+
}
70+
71+
belongs = node is FragmentSpread fragmentSpread && fragmentSpread.Name == fragment.Name;
72+
73+
if (node != null)
74+
{
75+
node.Visit(Visit, 0);
76+
}
77+
}
78+
79+
operation.Visit(Visit, 0);
80+
81+
return belongs;
82+
}
83+
3184
/// <inheritdoc />
32-
public async Task<INodeVisitor> ValidateAsync(ValidationContext context)
85+
public async ValueTask<INodeVisitor?> ValidateAsync(ValidationContext context)
3386
{
3487
await AuthorizeAsync(null, context.Schema, context, null);
3588
var operationType = OperationType.Query;
89+
var actualOperation = context.Document.Operations.FirstOrDefault(x => x.Name == context.OperationName) ?? context.Document.Operations.FirstOrDefault();
3690

3791
// this could leak info about hidden fields or types in error messages
3892
// it would be better to implement a filter on the Schema so it
3993
// acts as if they just don't exist vs. an auth denied error
4094
// - filtering the Schema is not currently supported
4195
// TODO: apply ISchemaFilter - context.Schema.Filter.AllowXXX
42-
4396
return new NodeVisitors(
4497
new MatchingNodeVisitor<Operation>((astType, context) =>
4598
{
99+
if (context.Document.Operations.Count > 1 && astType.Name != context.OperationName)
100+
{
101+
return;
102+
}
103+
46104
operationType = astType.OperationType;
47105

48106
var type = context.TypeInfo.GetLastType();
49107
AuthorizeAsync(astType, type, context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this
50108
}),
109+
51110
new MatchingNodeVisitor<ObjectField>((objectFieldAst, context) =>
52111
{
53-
if (context.TypeInfo.GetArgument()?.ResolvedType.GetNamedType() is IComplexGraphType argumentType)
112+
if (context.TypeInfo.GetArgument()?.ResolvedType?.GetNamedType() is IComplexGraphType argumentType && !ShouldBeSkipped(actualOperation, context))
54113
{
55114
var fieldType = argumentType.GetField(objectFieldAst.Name);
56115
AuthorizeAsync(objectFieldAst, fieldType, context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this
57116
}
58117
}),
118+
59119
new MatchingNodeVisitor<Field>((fieldAst, context) =>
60120
{
61121
var fieldDef = context.TypeInfo.GetFieldDef();
62122

63-
if (fieldDef == null)
123+
if (fieldDef == null || ShouldBeSkipped(actualOperation, context))
64124
return;
65125

66126
// check target field
67127
AuthorizeAsync(fieldAst, fieldDef, context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this
68128
// check returned graph type
69-
AuthorizeAsync(fieldAst, fieldDef.ResolvedType.GetNamedType(), context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this
129+
AuthorizeAsync(fieldAst, fieldDef.ResolvedType?.GetNamedType(), context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this
130+
}),
131+
132+
new MatchingNodeVisitor<VariableReference>((variableRef, context) =>
133+
{
134+
if (context.TypeInfo.GetArgument()?.ResolvedType?.GetNamedType() is not IComplexGraphType variableType || ShouldBeSkipped(actualOperation, context))
135+
return;
136+
137+
AuthorizeAsync(variableRef, variableType, context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this;
138+
139+
// Check each supplied field in the variable that exists in the variable type.
140+
// If some supplied field does not exist in the variable type then some other
141+
// validation rule should check that but here we should just ignore that
142+
// "unknown" field.
143+
if (context.Variables != null &&
144+
context.Variables.TryGetValue(variableRef.Name, out object input) &&
145+
input is Dictionary<string, object> fieldsValues)
146+
{
147+
foreach (var field in variableType.Fields)
148+
{
149+
if (fieldsValues.ContainsKey(field.Name))
150+
{
151+
AuthorizeAsync(variableRef, field, context, operationType).GetAwaiter().GetResult(); // TODO: need to think of something to avoid this;
152+
}
153+
}
154+
}
70155
})
71156
);
72157
}

src/Core/BasicGraphQLExecuter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ protected virtual ExecutionOptions GetOptions(string operationName, string query
4949
Schema = Schema,
5050
OperationName = operationName,
5151
Query = query,
52-
Inputs = variables,
52+
Variables = variables,
5353
UserContext = context,
5454
CancellationToken = cancellationToken,
5555
ComplexityConfiguration = _options.ComplexityConfiguration,

src/Core/Core.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
<ItemGroup>
1111
<!--TODO: remove SystemReactive in v6 -->
12-
<PackageReference Include="GraphQL.SystemReactive" Version="4.6.1" />
13-
<PackageReference Include="GraphQL.DataLoader" Version="4.6.1" />
12+
<PackageReference Include="GraphQL.SystemReactive" Version="5.0.0-preview-397" />
13+
<PackageReference Include="GraphQL.DataLoader" Version="5.0.0-preview-397" />
1414
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.0" />
1515
</ItemGroup>
1616

src/Core/DefaultGraphQLExecuter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ protected virtual ExecutionOptions GetOptions(string operationName, string query
5959
Schema = Schema,
6060
OperationName = operationName,
6161
Query = query,
62-
Inputs = variables,
62+
Variables = variables,
6363
UserContext = context,
6464
CancellationToken = cancellationToken,
6565
ComplexityConfiguration = _options.ComplexityConfiguration,

src/Core/GraphQLOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Threading.Tasks;
23
using GraphQL.Execution;
34
using GraphQL.Validation.Complexity;
45

@@ -16,7 +17,7 @@ public class GraphQLOptions
1617
/// </summary>
1718
public bool EnableMetrics { get; set; } = true;
1819

19-
public Action<UnhandledExceptionContext> UnhandledExceptionDelegate = ctx => { };
20+
public Func<UnhandledExceptionContext, Task> UnhandledExceptionDelegate = _ => Task.CompletedTask;
2021

2122
/// <summary>If set, limits the maximum number of nodes executed in parallel</summary>
2223
public int? MaxParallelExecutionCount { get; set; }

src/Transports.AspNetCore.NewtonsoftJson/Transports.AspNetCore.NewtonsoftJson.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<ProjectReference Include="..\Transports.AspNetCore\Transports.AspNetCore.csproj" />
11-
<PackageReference Include="GraphQL.NewtonsoftJson" Version="4.6.1" />
11+
<PackageReference Include="GraphQL.NewtonsoftJson" Version="5.0.0-preview-397" />
1212
</ItemGroup>
1313

1414
</Project>

src/Transports.AspNetCore.SystemTextJson/Transports.AspNetCore.SystemTextJson.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1111
<ProjectReference Include="..\Transports.AspNetCore\Transports.AspNetCore.csproj" />
1212
<ProjectReference Include="..\Transports.Subscriptions.Abstractions\Transports.Subscriptions.Abstractions.csproj" />
13-
<PackageReference Include="GraphQL.SystemTextJson" Version="4.6.1" />
13+
<PackageReference Include="GraphQL.SystemTextJson" Version="5.0.0-preview-397" />
1414
<PackageReference Include="System.IO.Pipelines" Version="5.0.1" />
1515
</ItemGroup>
1616

0 commit comments

Comments
 (0)