1
1
#nullable enable
2
2
3
3
using System . Collections . Generic ;
4
- using System . Linq ;
5
4
using System . Security . Claims ;
5
+ using System . Threading ;
6
6
using System . Threading . Tasks ;
7
- using GraphQL . Language . AST ;
8
7
using GraphQL . Types ;
9
8
using GraphQL . Validation ;
9
+ using GraphQLParser ;
10
+ using GraphQLParser . AST ;
11
+ using GraphQLParser . Visitors ;
10
12
using Microsoft . AspNetCore . Authorization ;
11
13
12
14
namespace GraphQL . Server . Authorization . AspNetCore
@@ -37,9 +39,9 @@ public AuthorizationValidationRule(
37
39
_messageBuilder = messageBuilder ;
38
40
}
39
41
40
- private bool ShouldBeSkipped ( Operation ? actualOperation , ValidationContext context )
42
+ private bool ShouldBeSkipped ( GraphQLOperationDefinition actualOperation , ValidationContext context )
41
43
{
42
- if ( context . Document . Operations . Count <= 1 )
44
+ if ( context . Document . OperationsCount ( ) <= 1 )
43
45
{
44
46
return false ;
45
47
}
@@ -59,76 +61,85 @@ private bool ShouldBeSkipped(Operation? actualOperation, ValidationContext conte
59
61
return true ;
60
62
}
61
63
62
- if ( ancestor is FragmentDefinition fragment )
64
+ if ( ancestor is GraphQLFragmentDefinition fragment )
63
65
{
64
- return ! FragmentBelongsToOperation ( fragment , actualOperation ) ;
66
+ //TODO: may be rewritten completely later
67
+ var c = new FragmentBelongsToOperationVisitorContext ( fragment ) ;
68
+ _visitor . VisitAsync ( actualOperation , c ) . GetAwaiter ( ) . GetResult ( ) ; // TODO: need to think of something to avoid this
69
+ return ! c . Found ;
65
70
}
66
71
} while ( true ) ;
67
72
}
68
73
69
- private bool FragmentBelongsToOperation ( FragmentDefinition fragment , Operation ? operation )
74
+ private sealed class FragmentBelongsToOperationVisitorContext : IASTVisitorContext
70
75
{
71
- bool belongs = false ;
72
- void Visit ( INode ? node , int _ )
76
+ public FragmentBelongsToOperationVisitorContext ( GraphQLFragmentDefinition fragment )
73
77
{
74
- if ( belongs )
75
- {
76
- return ;
77
- }
78
+ Fragment = fragment ;
79
+ }
78
80
79
- belongs = node is FragmentSpread fragmentSpread && fragmentSpread . Name == fragment . Name ;
81
+ public GraphQLFragmentDefinition Fragment { get ; }
80
82
81
- if ( node != null )
82
- {
83
- node . Visit ( Visit , 0 ) ;
84
- }
85
- }
83
+ public bool Found { get ; set ; }
84
+
85
+ public CancellationToken CancellationToken => default ;
86
+ }
86
87
87
- operation ? . Visit ( Visit , 0 ) ;
88
+ private static readonly FragmentBelongsToOperationVisitor _visitor = new ( ) ;
88
89
89
- return belongs ;
90
+ private sealed class FragmentBelongsToOperationVisitor : ASTVisitor < FragmentBelongsToOperationVisitorContext >
91
+ {
92
+ protected override ValueTask VisitFragmentSpreadAsync ( GraphQLFragmentSpread fragmentSpread , FragmentBelongsToOperationVisitorContext context )
93
+ {
94
+ context . Found = context . Fragment . FragmentName . Name == fragmentSpread . FragmentName . Name ;
95
+ return default ;
96
+ }
97
+
98
+ public override ValueTask VisitAsync ( ASTNode ? node , FragmentBelongsToOperationVisitorContext context )
99
+ {
100
+ return context . Found ? default : base . VisitAsync ( node , context ) ;
101
+ }
90
102
}
91
103
92
104
/// <inheritdoc />
93
105
public async ValueTask < INodeVisitor ? > ValidateAsync ( ValidationContext context )
94
106
{
95
107
await AuthorizeAsync ( null , context . Schema , context , null ) ;
96
108
var operationType = OperationType . Query ;
97
- var actualOperation = context . Document . Operations . FirstOrDefault ( x => x . Name == context . OperationName ) ?? context . Document . Operations . FirstOrDefault ( ) ;
98
109
99
110
// this could leak info about hidden fields or types in error messages
100
111
// it would be better to implement a filter on the Schema so it
101
112
// acts as if they just don't exist vs. an auth denied error
102
113
// - filtering the Schema is not currently supported
103
114
// TODO: apply ISchemaFilter - context.Schema.Filter.AllowXXX
104
115
return new NodeVisitors (
105
- new MatchingNodeVisitor < Operation > ( ( astType , context ) =>
116
+ new MatchingNodeVisitor < GraphQLOperationDefinition > ( ( astType , context ) =>
106
117
{
107
- if ( context . Document . Operations . Count > 1 && astType . Name != context . OperationName )
118
+ if ( context . Document . OperationsCount ( ) > 1 && astType . Name != context . Operation . Name )
108
119
{
109
120
return ;
110
121
}
111
122
112
- operationType = astType . OperationType ;
123
+ operationType = astType . Operation ;
113
124
114
125
var type = context . TypeInfo . GetLastType ( ) ;
115
126
AuthorizeAsync ( astType , type , context , operationType ) . GetAwaiter ( ) . GetResult ( ) ; // TODO: need to think of something to avoid this
116
127
} ) ,
117
128
118
- new MatchingNodeVisitor < ObjectField > ( ( objectFieldAst , context ) =>
129
+ new MatchingNodeVisitor < GraphQLObjectField > ( ( objectFieldAst , context ) =>
119
130
{
120
- if ( context . TypeInfo . GetArgument ( ) ? . ResolvedType ? . GetNamedType ( ) is IComplexGraphType argumentType && ! ShouldBeSkipped ( actualOperation , context ) )
131
+ if ( context . TypeInfo . GetArgument ( ) ? . ResolvedType ? . GetNamedType ( ) is IComplexGraphType argumentType && ! ShouldBeSkipped ( context . Operation , context ) )
121
132
{
122
133
var fieldType = argumentType . GetField ( objectFieldAst . Name ) ;
123
134
AuthorizeAsync ( objectFieldAst , fieldType , context , operationType ) . GetAwaiter ( ) . GetResult ( ) ; // TODO: need to think of something to avoid this
124
135
}
125
136
} ) ,
126
137
127
- new MatchingNodeVisitor < Field > ( ( fieldAst , context ) =>
138
+ new MatchingNodeVisitor < GraphQLField > ( ( fieldAst , context ) =>
128
139
{
129
140
var fieldDef = context . TypeInfo . GetFieldDef ( ) ;
130
141
131
- if ( fieldDef == null || ShouldBeSkipped ( actualOperation , context ) )
142
+ if ( fieldDef == null || ShouldBeSkipped ( context . Operation , context ) )
132
143
return ;
133
144
134
145
// check target field
@@ -137,9 +148,9 @@ void Visit(INode? node, int _)
137
148
AuthorizeAsync ( fieldAst , fieldDef . ResolvedType ? . GetNamedType ( ) , context , operationType ) . GetAwaiter ( ) . GetResult ( ) ; // TODO: need to think of something to avoid this
138
149
} ) ,
139
150
140
- new MatchingNodeVisitor < VariableReference > ( ( variableRef , context ) =>
151
+ new MatchingNodeVisitor < GraphQLVariable > ( ( variableRef , context ) =>
141
152
{
142
- if ( context . TypeInfo . GetArgument ( ) ? . ResolvedType ? . GetNamedType ( ) is not IComplexGraphType variableType || ShouldBeSkipped ( actualOperation , context ) )
153
+ if ( context . TypeInfo . GetArgument ( ) ? . ResolvedType ? . GetNamedType ( ) is not IComplexGraphType variableType || ShouldBeSkipped ( context . Operation , context ) )
143
154
return ;
144
155
145
156
AuthorizeAsync ( variableRef , variableType , context , operationType ) . GetAwaiter ( ) . GetResult ( ) ; // TODO: need to think of something to avoid this;
@@ -149,7 +160,7 @@ void Visit(INode? node, int _)
149
160
// validation rule should check that but here we should just ignore that
150
161
// "unknown" field.
151
162
if ( context . Variables != null &&
152
- context . Variables . TryGetValue ( variableRef . Name , out object ? input ) &&
163
+ context . Variables . TryGetValue ( variableRef . Name . StringValue , out object ? input ) && //ISSUE:allocation
153
164
input is Dictionary < string , object > fieldsValues )
154
165
{
155
166
foreach ( var field in variableType . Fields )
@@ -164,7 +175,7 @@ void Visit(INode? node, int _)
164
175
) ;
165
176
}
166
177
167
- private async Task AuthorizeAsync ( INode ? node , IProvideMetadata ? provider , ValidationContext context , OperationType ? operationType )
178
+ private async Task AuthorizeAsync ( ASTNode ? node , IProvideMetadata ? provider , ValidationContext context , OperationType ? operationType )
168
179
{
169
180
var policyNames = provider ? . GetPolicies ( ) ;
170
181
@@ -198,7 +209,7 @@ private async Task AuthorizeAsync(INode? node, IProvideMetadata? provider, Valid
198
209
/// <summary>
199
210
/// Adds an authorization failure error to the document response
200
211
/// </summary>
201
- protected virtual void AddValidationError ( INode ? node , ValidationContext context , OperationType ? operationType , AuthorizationResult result )
212
+ protected virtual void AddValidationError ( ASTNode ? node , ValidationContext context , OperationType ? operationType , AuthorizationResult result )
202
213
{
203
214
string message = _messageBuilder . GenerateMessage ( operationType , result ) ;
204
215
context . ReportError ( new AuthorizationError ( node , context , message , result , operationType ) ) ;
0 commit comments