-
-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathAuthorizationValidationRule.cs
More file actions
124 lines (106 loc) · 5.02 KB
/
AuthorizationValidationRule.cs
File metadata and controls
124 lines (106 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using System.Collections.Generic;
using System.Threading.Tasks;
using GraphQL.Language.AST;
using GraphQL.Types;
using GraphQL.Validation;
namespace GraphQL.Authorization
{
/// <summary>
/// GraphQL authorization validation rule which evaluates configured
/// (via policies) requirements on schema elements: types, fields, etc.
/// </summary>
public class AuthorizationValidationRule : IValidationRule
{
private readonly IAuthorizationEvaluator _evaluator;
/// <summary>
/// Creates an instance of <see cref="AuthorizationValidationRule"/> with
/// the specified authorization evaluator.
/// </summary>
public AuthorizationValidationRule(IAuthorizationEvaluator evaluator)
{
_evaluator = evaluator;
}
/// <inheritdoc />
public Task<INodeVisitor> ValidateAsync(ValidationContext context)
{
var userContext = context.UserContext as IProvideClaimsPrincipal;
var operationType = OperationType.Query;
// this could leak info about hidden fields or types in error messages
// it would be better to implement a filter on the Schema so it
// acts as if they just don't exist vs. an auth denied error
// - filtering the Schema is not currently supported
// TODO: apply ISchemaFilter - context.Schema.Filter.AllowXXX
return Task.FromResult((INodeVisitor)new NodeVisitors(
new MatchingNodeVisitor<Operation>((astType, context) =>
{
operationType = astType.OperationType;
var type = context.TypeInfo.GetLastType();
CheckAuth(astType, type, userContext, context, operationType);
}),
new MatchingNodeVisitor<ObjectField>((objectFieldAst, context) =>
{
if (context.TypeInfo.GetArgument()?.ResolvedType.GetNamedType() is IComplexGraphType argumentType)
{
var fieldType = argumentType.GetField(objectFieldAst.Name);
CheckAuth(objectFieldAst, fieldType, userContext, context, operationType);
}
}),
new MatchingNodeVisitor<Field>((fieldAst, context) =>
{
var fieldDef = context.TypeInfo.GetFieldDef();
if (fieldDef == null)
return;
// check target field
CheckAuth(fieldAst, fieldDef, userContext, context, operationType);
// check returned graph type
CheckAuth(fieldAst, fieldDef.ResolvedType.GetNamedType(), userContext, context, operationType);
}),
new MatchingNodeVisitor<VariableReference>((variableRef, context) =>
{
if (!(context.TypeInfo.GetArgument().ResolvedType.GetNamedType() is IComplexGraphType variableType))
return;
CheckAuth(variableRef, variableType, userContext, context, operationType);
// Check each supplied field in the variable that exists in the variable type.
// If some supplied field does not exist in the variable type then some other
// validation rule should check that but here we should just ignore that
// "unknown" field.
if (context.Inputs != null &&
context.Inputs.TryGetValue(variableRef.Name, out object input) &&
input is Dictionary<string, object> fieldsValues)
{
foreach (var field in variableType.Fields)
{
if (fieldsValues.ContainsKey(field.Name))
{
CheckAuth(variableRef, field, userContext, context, operationType);
}
}
}
})
));
}
private void CheckAuth(
INode node,
IProvideMetadata provider,
IProvideClaimsPrincipal userContext,
ValidationContext context,
OperationType? operationType)
{
if (provider == null || !provider.RequiresAuthorization())
return;
// TODO: async -> sync transition
var result = _evaluator
.Evaluate(userContext?.User, context.UserContext, context.Inputs, provider.GetPolicies())
.GetAwaiter()
.GetResult();
if (result.Succeeded)
return;
string errors = string.Join("\n", result.Errors);
context.ReportError(new ValidationError(
context.Document.OriginalQuery,
"authorization",
$"You are not authorized to run this {operationType.ToString().ToLower()}.\n{errors}",
node));
}
}
}