@@ -416,6 +416,12 @@ def get_meta(self, options):
416416 meta ['description' ] = self .get_description ()
417417 if 'nb_objs' in options :
418418 meta ['nb_objs' ] = self .get_nb_objs ()
419+ if 'objs_stats' in options :
420+ if 'nb_objs' in meta :
421+ total = meta ['nb_objs' ]
422+ else :
423+ total = None
424+ meta ['objs_stats' ] = self .get_objs_stats (total = total )
419425 if 'tags' in options :
420426 meta ['tags' ] = self .get_tags ()
421427 if 'filters' in options :
@@ -456,6 +462,12 @@ def get_nb_objs(self):
456462 objs [obj_type ] = nb
457463 return objs
458464
465+ def get_nb_total_objs (self ):
466+ nb = 0
467+ for obj_type in get_objects_tracked ():
468+ nb += self .get_nb_objs_by_type (obj_type )
469+ return nb
470+
459471 def get_objs (self ):
460472 objs = []
461473 for obj_type in get_objects_tracked ():
@@ -488,6 +500,9 @@ def get_objs_by_daterange(self, date_from, date_to, obj_types):
488500 def get_obj_dates (self , obj_type , subtype , obj_id ):
489501 return r_tracker .smembers (f'obj:tracker:{ obj_type } :{ subtype } :{ obj_id } :{ self .uuid } ' )
490502
503+ def is_tracked_obj (self , obj_gid ):
504+ return r_tracker .sismember (f'obj:trackers:{ obj_gid } ' , self .uuid )
505+
491506 # - TODO Data Retention TO Implement - #
492507 # Or Daily/Monthly Global DB Cleanup:
493508 # Iterate on each tracker:
@@ -516,15 +531,95 @@ def add(self, obj_type, subtype, obj_id, date=None):
516531 def remove (self , obj_type , subtype , obj_id ):
517532 if not subtype :
518533 subtype = ''
534+ obj_gid = f'{ obj_type } :{ subtype } :{ obj_id } '
519535
520536 for date in self .get_obj_dates (obj_type , subtype , obj_id ):
521- r_tracker .srem (f'tracker:objs:{ self .uuid } :{ date } ' , f' { obj_type } : { subtype } : { obj_id } ' )
537+ r_tracker .srem (f'tracker:objs:{ self .uuid } :{ date } ' , obj_gid )
522538 r_tracker .srem (f'obj:tracker:{ obj_type } :{ subtype } :{ obj_id } :{ self .uuid } ' , date )
523539
524540 r_tracker .srem (f'obj:trackers:{ obj_type } :{ subtype } :{ obj_id } ' , self .uuid )
525541 r_tracker .srem (f'tracker:objs:{ self .uuid } :{ obj_type } ' , f'{ subtype } :{ obj_id } ' )
542+ # obj status
543+ self .delete_obj_status (obj_gid )
544+
526545 self .update_daterange ()
527546
547+ def get_nb_objs_read (self ):
548+ return r_tracker .scard (f'tracker:objs:read:{ self .uuid } ' )
549+
550+ def get_objs_done (self ):
551+ return r_tracker .smembers (f'tracker:objs:done:{ self .uuid } ' )
552+
553+ def get_nb_objs_done (self ):
554+ return r_tracker .scard (f'tracker:objs:done:{ self .uuid } ' )
555+
556+ def get_objs_rejected (self ):
557+ return r_tracker .smembers (f'tracker:objs:fp:{ self .uuid } ' )
558+
559+ def get_nb_objs_rejected (self ):
560+ return r_tracker .scard (f'tracker:objs:fp:{ self .uuid } ' )
561+
562+ def get_objs_stats (self , total = None ):
563+ done = self .get_nb_objs_done ()
564+ fp = self .get_nb_objs_rejected ()
565+ read = self .get_nb_objs_read ()
566+ if total :
567+ nb = 0
568+ for nb_obj in total :
569+ nb += total [nb_obj ]
570+ else :
571+ nb = self .get_nb_total_objs ()
572+ unread = nb - done - fp - read
573+ return {'done' : done , 'fp' : fp , 'read' : read , 'unread' : unread }
574+
575+ def is_obj_read (self , obj_gid ):
576+ return r_tracker .sismember (f'tracker:objs:read:{ self .uuid } ' , obj_gid )
577+
578+ def is_obj_done (self , obj_gid ):
579+ return r_tracker .sismember (f'tracker:objs:done:{ self .uuid } ' , obj_gid )
580+
581+ def is_obj_rejected (self , obj_gid ):
582+ return r_tracker .sismember (f'tracker:objs:fp:{ self .uuid } ' , obj_gid )
583+
584+ def get_obj_status (self , obj_gid ):
585+ if self .is_obj_read (obj_gid ):
586+ return 'read'
587+ elif self .is_obj_done (obj_gid ):
588+ return 'done'
589+ elif self .is_obj_rejected (obj_gid ):
590+ return 'rejected'
591+ else :
592+ return 'unread'
593+
594+ def obj_read (self , obj_gid ):
595+ self .obj_undone (obj_gid )
596+ self .obj_unreject (obj_gid )
597+ r_tracker .sadd (f'tracker:objs:read:{ self .uuid } ' , obj_gid )
598+
599+ def obj_unread (self , obj_gid ):
600+ r_tracker .srem (f'tracker:objs:read:{ self .uuid } ' , obj_gid )
601+
602+ def obj_done (self , obj_gid ):
603+ self .obj_unread (obj_gid )
604+ self .obj_unreject (obj_gid )
605+ r_tracker .sadd (f'tracker:objs:done:{ self .uuid } ' , obj_gid )
606+
607+ def obj_undone (self , obj_gid ):
608+ r_tracker .srem (f'tracker:objs:done:{ self .uuid } ' , obj_gid )
609+
610+ def obj_reject (self , obj_gid ):
611+ self .obj_unread (obj_gid )
612+ self .obj_undone (obj_gid )
613+ r_tracker .sadd (f'tracker:objs:fp:{ self .uuid } ' , obj_gid )
614+
615+ def obj_unreject (self , obj_gid ):
616+ r_tracker .srem (f'tracker:objs:fp:{ self .uuid } ' , obj_gid )
617+
618+ def delete_obj_status (self , obj_gid ):
619+ self .obj_unread (obj_gid )
620+ self .obj_undone (obj_gid )
621+ self .obj_unreject (obj_gid )
622+
528623 # TODO escape custom tags
529624 # TODO escape mails ????
530625 def create (self , tracker_type , to_track , org , user_id , level , description = None , filters = {}, tags = [], mails = [], webhook = None ):
@@ -730,6 +825,10 @@ def delete(self):
730825 ail_orgs .remove_obj_to_org (self .get_org (), 'tracker' , self .uuid )
731826 # meta
732827 r_tracker .delete (f'tracker:{ self .uuid } ' )
828+ # objs status
829+ r_tracker .delete (f'tracker:objs:read:{ self .uuid } ' )
830+ r_tracker .delete (f'tracker:objs:done:{ self .uuid } ' )
831+ r_tracker .delete (f'tracker:objs:fp:{ self .uuid } ' )
733832 trigger_trackers_refresh (tracker_type )
734833
735834
@@ -932,6 +1031,9 @@ def is_obj_tracked(obj_type, subtype, obj_id):
9321031def get_obj_trackers (obj_type , subtype , obj_id ):
9331032 return r_tracker .smembers (f'obj:trackers:{ obj_type } :{ subtype } :{ obj_id } ' )
9341033
1034+ def set_obj_tracker_read (tracker_uuid , obj_gid ):
1035+ r_tracker .sadd (f'tracker:objs:read:{ tracker_uuid } ' , obj_gid )
1036+
9351037def delete_obj_trackers (obj_type , subtype , obj_id ):
9361038 for tracker_uuid in get_obj_trackers (obj_type , subtype , obj_id ):
9371039 tracker = Tracker (tracker_uuid )
@@ -1081,8 +1183,9 @@ def api_validate_tracker_to_add(to_track, tracker_type, nb_words=1):
10811183 return {"status" : "error" , "reason" : "Invalid domain name" }, 400
10821184
10831185 elif tracker_type == 'yara_custom' :
1084- if not is_valid_yara_rule (to_track ):
1085- return {"status" : "error" , "reason" : "Invalid custom Yara Rule" }, 400
1186+ valid_yara_rule , error = is_valid_yara_rule (to_track )
1187+ if not valid_yara_rule :
1188+ return {"status" : "error" , "reason" : f"Invalid Yara Rule: { error } " }, 400
10861189 elif tracker_type == 'yara_default' :
10871190 if not is_valid_default_yara_rule (to_track ):
10881191 return {"status" : "error" , "reason" : "The Yara Rule doesn't exist" }, 400
@@ -1282,6 +1385,54 @@ def api_tracker_remove_object(data, user_org, user_id, user_role):
12821385 return {"status" : "error" , "reason" : "Invalid Object" }, 400
12831386 return tracker .remove (obj_type , subtype , obj_id ), 200
12841387
1388+ def api_tracker_object_status_done (data , user_org , user_id , user_role ):
1389+ tracker_uuid = data .get ('uuid' )
1390+ res = api_check_tracker_acl (tracker_uuid , user_org , user_id , user_role , 'edit' )
1391+ if res :
1392+ return res
1393+
1394+ tracker = Tracker (tracker_uuid )
1395+ object_gid = data .get ('gid' )
1396+ if not tracker .is_tracked_obj (object_gid ):
1397+ return {"status" : "error" , "reason" : "Not Tracked Object" }, 404
1398+ return tracker .obj_done (object_gid ), 200
1399+
1400+ def api_tracker_object_status_reject (data , user_org , user_id , user_role ):
1401+ tracker_uuid = data .get ('uuid' )
1402+ res = api_check_tracker_acl (tracker_uuid , user_org , user_id , user_role , 'edit' )
1403+ if res :
1404+ return res
1405+
1406+ tracker = Tracker (tracker_uuid )
1407+ object_gid = data .get ('gid' )
1408+ if not tracker .is_tracked_obj (object_gid ):
1409+ return {"status" : "error" , "reason" : "Not Tracked Object" }, 404
1410+ return tracker .obj_reject (object_gid ), 200
1411+
1412+ def api_tracker_object_status_unread (data , user_org , user_id , user_role ):
1413+ tracker_uuid = data .get ('uuid' )
1414+ res = api_check_tracker_acl (tracker_uuid , user_org , user_id , user_role , 'edit' )
1415+ if res :
1416+ return res
1417+
1418+ tracker = Tracker (tracker_uuid )
1419+ object_gid = data .get ('gid' )
1420+ if not tracker .is_tracked_obj (object_gid ):
1421+ return {"status" : "error" , "reason" : "Not Tracked Object" }, 404
1422+ return tracker .delete_obj_status (object_gid ), 200
1423+
1424+ def api_tracker_object_status_read (data , user_org , user_id , user_role ):
1425+ tracker_uuid = data .get ('uuid' )
1426+ res = api_check_tracker_acl (tracker_uuid , user_org , user_id , user_role , 'edit' )
1427+ if res :
1428+ return res
1429+
1430+ tracker = Tracker (tracker_uuid )
1431+ object_gid = data .get ('gid' )
1432+ if not tracker .is_tracked_obj (object_gid ):
1433+ return {"status" : "error" , "reason" : "Not Tracked Object" }, 404
1434+ return tracker .obj_read (object_gid ), 200
1435+
12851436## -- CREATE TRACKER -- ##
12861437
12871438####################
@@ -1411,9 +1562,9 @@ def reload_yara_rules():
14111562def is_valid_yara_rule (yara_rule ):
14121563 try :
14131564 yara .compile (source = yara_rule )
1414- return True
1415- except :
1416- return False
1565+ return True , None
1566+ except Exception as e :
1567+ return False , str ( e )
14171568
14181569def is_default_yara_rule (tracked_yara_name ):
14191570 yara_dir = get_yara_rules_dir ()
@@ -2000,8 +2151,9 @@ def api_resume_retro_hunt_task(user_org, user_id, user_role, task_uuid):
20002151
20012152def api_validate_rule_to_add (rule , rule_type ):
20022153 if rule_type == 'yara_custom' :
2003- if not is_valid_yara_rule (rule ):
2004- return {"status" : "error" , "reason" : "Invalid custom Yara Rule" }, 400
2154+ valid_yara_rule , error = is_valid_yara_rule (rule )
2155+ if not valid_yara_rule :
2156+ return {"status" : "error" , "reason" : f"Invalid Yara Rule: { e } " }, 400
20052157 elif rule_type == 'yara_default' :
20062158 if not is_valid_default_yara_rule (rule ):
20072159 return {"status" : "error" , "reason" : "The Yara Rule doesn't exist" }, 400
0 commit comments