1
+ using System . Net ;
1
2
using JetBrains . Annotations ;
2
3
using JsonApiDotNetCore . AtomicOperations ;
3
4
using JsonApiDotNetCore . Configuration ;
4
5
using JsonApiDotNetCore . Errors ;
5
6
using JsonApiDotNetCore . Middleware ;
6
7
using JsonApiDotNetCore . Resources ;
8
+ using JsonApiDotNetCore . Serialization . Objects ;
7
9
using Microsoft . AspNetCore . Mvc ;
8
10
using Microsoft . AspNetCore . Mvc . ModelBinding ;
9
11
using Microsoft . Extensions . Logging ;
@@ -22,23 +24,26 @@ public abstract class BaseJsonApiOperationsController : CoreJsonApiController
22
24
private readonly IOperationsProcessor _processor ;
23
25
private readonly IJsonApiRequest _request ;
24
26
private readonly ITargetedFields _targetedFields ;
27
+ private readonly IAtomicOperationFilter _operationFilter ;
25
28
private readonly TraceLogWriter < BaseJsonApiOperationsController > _traceWriter ;
26
29
27
30
protected BaseJsonApiOperationsController ( IJsonApiOptions options , IResourceGraph resourceGraph , ILoggerFactory loggerFactory ,
28
- IOperationsProcessor processor , IJsonApiRequest request , ITargetedFields targetedFields )
31
+ IOperationsProcessor processor , IJsonApiRequest request , ITargetedFields targetedFields , IAtomicOperationFilter operationFilter )
29
32
{
30
33
ArgumentGuard . NotNull ( options ) ;
31
34
ArgumentGuard . NotNull ( resourceGraph ) ;
32
35
ArgumentGuard . NotNull ( loggerFactory ) ;
33
36
ArgumentGuard . NotNull ( processor ) ;
34
37
ArgumentGuard . NotNull ( request ) ;
35
38
ArgumentGuard . NotNull ( targetedFields ) ;
39
+ ArgumentGuard . NotNull ( operationFilter ) ;
36
40
37
41
_options = options ;
38
42
_resourceGraph = resourceGraph ;
39
43
_processor = processor ;
40
44
_request = request ;
41
45
_targetedFields = targetedFields ;
46
+ _operationFilter = operationFilter ;
42
47
_traceWriter = new TraceLogWriter < BaseJsonApiOperationsController > ( loggerFactory ) ;
43
48
}
44
49
@@ -111,6 +116,8 @@ public virtual async Task<IActionResult> PostOperationsAsync([FromBody] IList<Op
111
116
112
117
ArgumentGuard . NotNull ( operations ) ;
113
118
119
+ ValidateEnabledOperations ( operations ) ;
120
+
114
121
if ( _options . ValidateModelState )
115
122
{
116
123
ValidateModelState ( operations ) ;
@@ -120,6 +127,68 @@ public virtual async Task<IActionResult> PostOperationsAsync([FromBody] IList<Op
120
127
return results . Any ( result => result != null ) ? Ok ( results ) : NoContent ( ) ;
121
128
}
122
129
130
+ protected virtual void ValidateEnabledOperations ( IList < OperationContainer > operations )
131
+ {
132
+ List < ErrorObject > errors = [ ] ;
133
+
134
+ for ( int operationIndex = 0 ; operationIndex < operations . Count ; operationIndex ++ )
135
+ {
136
+ IJsonApiRequest operationRequest = operations [ operationIndex ] . Request ;
137
+ WriteOperationKind operationKind = operationRequest . WriteOperation ! . Value ;
138
+
139
+ if ( operationRequest . Relationship != null && ! _operationFilter . IsEnabled ( operationRequest . Relationship . LeftType , operationKind ) )
140
+ {
141
+ string operationCode = GetOperationCodeText ( operationKind ) ;
142
+
143
+ errors . Add ( new ErrorObject ( HttpStatusCode . UnprocessableEntity )
144
+ {
145
+ Title = "The requested operation is not accessible." ,
146
+ Detail = $ "The '{ operationCode } ' relationship operation is not accessible for relationship '{ operationRequest . Relationship } ' " +
147
+ $ "on resource type '{ operationRequest . Relationship . LeftType } '.",
148
+ Source = new ErrorSource
149
+ {
150
+ Pointer = $ "/atomic:operations[{ operationIndex } ]"
151
+ }
152
+ } ) ;
153
+ }
154
+ else if ( operationRequest . PrimaryResourceType != null && ! _operationFilter . IsEnabled ( operationRequest . PrimaryResourceType , operationKind ) )
155
+ {
156
+ string operationCode = GetOperationCodeText ( operationKind ) ;
157
+
158
+ errors . Add ( new ErrorObject ( HttpStatusCode . UnprocessableEntity )
159
+ {
160
+ Title = "The requested operation is not accessible." ,
161
+ Detail = $ "The '{ operationCode } ' resource operation is not accessible for resource type '{ operationRequest . PrimaryResourceType } '.",
162
+ Source = new ErrorSource
163
+ {
164
+ Pointer = $ "/atomic:operations[{ operationIndex } ]"
165
+ }
166
+ } ) ;
167
+ }
168
+ }
169
+
170
+ if ( errors . Count > 0 )
171
+ {
172
+ throw new JsonApiException ( errors ) ;
173
+ }
174
+ }
175
+
176
+ private static string GetOperationCodeText ( WriteOperationKind operationKind )
177
+ {
178
+ AtomicOperationCode operationCode = operationKind switch
179
+ {
180
+ WriteOperationKind . CreateResource => AtomicOperationCode . Add ,
181
+ WriteOperationKind . UpdateResource => AtomicOperationCode . Update ,
182
+ WriteOperationKind . DeleteResource => AtomicOperationCode . Remove ,
183
+ WriteOperationKind . AddToRelationship => AtomicOperationCode . Add ,
184
+ WriteOperationKind . SetRelationship => AtomicOperationCode . Update ,
185
+ WriteOperationKind . RemoveFromRelationship => AtomicOperationCode . Remove ,
186
+ _ => throw new NotSupportedException ( $ "Unknown operation kind '{ operationKind } '.")
187
+ } ;
188
+
189
+ return operationCode . ToString ( ) . ToLowerInvariant ( ) ;
190
+ }
191
+
123
192
protected virtual void ValidateModelState ( IList < OperationContainer > operations )
124
193
{
125
194
// We must validate the resource inside each operation manually, because they are typed as IIdentifiable.
0 commit comments