1
1
import logging
2
2
3
3
from api_app import models , serializers , helpers
4
+ from api_app .permissions import ExtendedObjectPermissions
4
5
from .script_analyzers import general
5
6
6
7
from wsgiref .util import FileWrapper
10
11
from rest_framework .response import Response
11
12
from rest_framework import status , viewsets
12
13
from rest_framework .decorators import api_view
14
+ from rest_framework .permissions import DjangoObjectPermissions
15
+ from guardian .decorators import permission_required_or_403
16
+ from rest_framework_guardian .filters import ObjectPermissionsFilter
13
17
14
18
15
19
logger = logging .getLogger (__name__ )
19
23
20
24
21
25
@api_view (["GET" ])
26
+ @permission_required_or_403 ("api_app.view_job" )
22
27
def ask_analysis_availability (request ):
23
28
"""
24
29
This is useful to avoid repeating the same analysis multiple times.
@@ -115,12 +120,13 @@ def ask_analysis_availability(request):
115
120
except Exception as e :
116
121
logger .exception (f"ask_analysis_availability requester:{ source } error:{ e } ." )
117
122
return Response (
118
- {"error " : "error in ask_analysis_availability. Check logs." },
123
+ {"detail " : "error in ask_analysis_availability. Check logs." },
119
124
status = status .HTTP_500_INTERNAL_SERVER_ERROR ,
120
125
)
121
126
122
127
123
128
@api_view (["POST" ])
129
+ @permission_required_or_403 ("api_app.add_job" )
124
130
def send_analysis_request (request ):
125
131
"""
126
132
This endpoint allows to start a Job related to a file or an observable
@@ -145,6 +151,9 @@ def send_analysis_request(request):
145
151
list of id's of tags to apply to job
146
152
:param [run_all_available_analyzers]: bool
147
153
default False
154
+ :param [private]: bool
155
+ default False,
156
+ enable it to allow view permissions to only requesting user's groups.
148
157
:param [force_privacy]: bool
149
158
default False,
150
159
enable it if you want to avoid to run analyzers with privacy issues
@@ -172,7 +181,9 @@ def send_analysis_request(request):
172
181
173
182
params = {"source" : source }
174
183
175
- serializer = serializers .JobSerializer (data = data_received )
184
+ serializer = serializers .JobSerializer (
185
+ data = data_received , context = {"request" : request }
186
+ )
176
187
if serializer .is_valid ():
177
188
serialized_data = serializer .validated_data
178
189
logger .info (f"serialized_data: { serialized_data } " )
@@ -234,20 +245,20 @@ def send_analysis_request(request):
234
245
235
246
# save the arrived data plus new params into a new job object
236
247
serializer .save (** params )
237
- job_id = serializer .data .get ("id" , "" )
248
+ job_id = serializer .data .get ("id" , None )
238
249
md5 = serializer .data .get ("md5" , "" )
239
- logger .info (f"new job_id { job_id } for md5 { md5 } " )
250
+ logger .info (f"New Job added with ID: # { job_id } and md5: { md5 } . " )
240
251
if not job_id :
241
252
return Response ({"error" : "815" }, status = status .HTTP_400_BAD_REQUEST )
242
253
243
254
else :
244
255
error_message = f"serializer validation failed: { serializer .errors } "
245
- logger .info (error_message )
256
+ logger .error (error_message )
246
257
return Response (
247
258
{"error" : error_message }, status = status .HTTP_400_BAD_REQUEST
248
259
)
249
260
250
- is_sample = serializer .data .get ("is_sample" , "" )
261
+ is_sample = serializer .data .get ("is_sample" , False )
251
262
if not test :
252
263
general .start_analyzers (
253
264
params ["analyzers_to_execute" ], analyzers_config , job_id , md5 , is_sample
@@ -267,12 +278,13 @@ def send_analysis_request(request):
267
278
except Exception as e :
268
279
logger .exception (f"receive_analysis_request requester:{ source } error:{ e } ." )
269
280
return Response (
270
- {"error " : "error in send_analysis_request. Check logs" },
281
+ {"detail " : "error in send_analysis_request. Check logs" },
271
282
status = status .HTTP_500_INTERNAL_SERVER_ERROR ,
272
283
)
273
284
274
285
275
286
@api_view (["GET" ])
287
+ @permission_required_or_403 ("api_app.view_job" )
276
288
def ask_analysis_result (request ):
277
289
"""
278
290
Endpoint to retrieve the status and results of a specific Job based on its ID
@@ -299,6 +311,12 @@ def ask_analysis_result(request):
299
311
job_id = data_received ["job_id" ]
300
312
try :
301
313
job = models .Job .objects .get (id = job_id )
314
+ # check permission
315
+ if not request .user .has_perm ("api_app.view_job" , job ):
316
+ return Response (
317
+ {"detail" : "You don't have permission to perform this operation." },
318
+ status = status .HTTP_403_FORBIDDEN ,
319
+ )
302
320
except models .Job .DoesNotExist :
303
321
response_dict = {"status" : "not_available" }
304
322
else :
@@ -308,7 +326,7 @@ def ask_analysis_result(request):
308
326
"job_id" : str (job .id ),
309
327
}
310
328
# adding elapsed time
311
- finished_analysis_time = getattr (job , "finished_analysis_time" , "" )
329
+ finished_analysis_time = getattr (job , "finished_analysis_time" , None )
312
330
if not finished_analysis_time :
313
331
finished_analysis_time = helpers .get_now ()
314
332
elapsed_time = finished_analysis_time - job .received_request_time
@@ -360,32 +378,37 @@ def download_sample(request):
360
378
"""
361
379
this method is used to download a sample from a Job ID
362
380
:param request: job_id
363
- :return 200 found, 404 not found
381
+ :returns: 200 if found, 404 not found, 403 forbidden
364
382
"""
365
383
try :
366
384
data_received = request .query_params
367
385
logger .info (f"Get binary by Job ID. Data received { data_received } " )
368
386
if "job_id" not in data_received :
369
387
return Response ({"error" : "821" }, status = status .HTTP_400_BAD_REQUEST )
388
+ # get job object
370
389
try :
371
390
job = models .Job .objects .get (id = data_received ["job_id" ])
372
391
except models .Job .DoesNotExist :
373
- return Response ({"answer" : "not found" }, status = status .HTTP_200_OK )
392
+ return Response ({"detail" : "not found" }, status = status .HTTP_404_NOT_FOUND )
393
+ # check permission
394
+ if not request .user .has_perm ("api_app.view_job" , job ):
395
+ return Response (
396
+ {"detail" : "You don't have permission to perform this operation." },
397
+ status = status .HTTP_403_FORBIDDEN ,
398
+ )
399
+ # make sure it is a sample
374
400
if not job .is_sample :
375
401
return Response (
376
- {"answer " : "job without sample" }, status = status .HTTP_400_BAD_REQUEST
402
+ {"detail " : "job without sample" }, status = status .HTTP_400_BAD_REQUEST
377
403
)
378
- file_mimetype = job .file_mimetype
379
- response = HttpResponse (FileWrapper (job .file ), content_type = file_mimetype )
380
- response ["Content-Disposition" ] = "attachment; filename={}" .format (
381
- job .file_name
382
- )
404
+ response = HttpResponse (FileWrapper (job .file ), content_type = job .file_mimetype )
405
+ response ["Content-Disposition" ] = f"attachment; filename={ job .file_name } "
383
406
return response
384
407
385
408
except Exception as e :
386
409
logger .exception (f"download_sample requester:{ str (request .user )} error:{ e } ." )
387
410
return Response (
388
- {"error " : "error in download_sample. Check logs." },
411
+ {"detail " : "error in download_sample. Check logs." },
389
412
status = status .HTTP_500_INTERNAL_SERVER_ERROR ,
390
413
)
391
414
@@ -406,23 +429,31 @@ class JobViewSet(viewsets.ReadOnlyModelViewSet):
406
429
if wrong HTTP method
407
430
"""
408
431
409
- queryset = models .Job .objects .all ()
432
+ queryset = models .Job .objects .order_by ( "-received_request_time" ). all ()
410
433
serializer_class = serializers .JobSerializer
411
-
412
- def list (self , request ):
413
- queryset = (
414
- models .Job .objects .order_by ("-received_request_time" )
415
- .defer ("analysis_reports" , "errors" )
416
- .all ()
417
- )
418
- serializer = serializers .JobListSerializer (queryset , many = True )
419
- return Response (serializer .data )
434
+ serializer_action_classes = {
435
+ "list" : serializers .JobListSerializer ,
436
+ }
437
+ permission_classes = (ExtendedObjectPermissions ,)
438
+ filter_backends = (ObjectPermissionsFilter ,)
439
+
440
+ def get_serializer_class (self , * args , ** kwargs ):
441
+ """
442
+ Instantiate the list of serializers per action from class attribute
443
+ (must be defined).
444
+ """
445
+ kwargs ["partial" ] = True
446
+ try :
447
+ return self .serializer_action_classes [self .action ]
448
+ except (KeyError , AttributeError ):
449
+ return super (JobViewSet , self ).get_serializer_class ()
420
450
421
451
422
452
class TagViewSet (viewsets .ModelViewSet ):
423
453
"""
424
454
REST endpoint to pefrom CRUD operations on Job tags.
425
455
Requires authentication.
456
+ POST/PUT/DELETE requires model/object level permission.
426
457
427
458
:methods_allowed:
428
459
GET, POST, PUT, DELETE, OPTIONS
@@ -437,3 +468,4 @@ class TagViewSet(viewsets.ModelViewSet):
437
468
438
469
queryset = models .Tag .objects .all ()
439
470
serializer_class = serializers .TagSerializer
471
+ permission_classes = (DjangoObjectPermissions ,)
0 commit comments