Skip to content

Commit e317029

Browse files
committed
chg: [tracker] add tracked objects status: viewed, done, rejected (fp) + improve invalid yara-rule error message
1 parent eb205e4 commit e317029

File tree

3 files changed

+338
-13
lines changed

3 files changed

+338
-13
lines changed

bin/lib/Tracker.py

Lines changed: 160 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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):
9321031
def 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+
9351037
def 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():
14111562
def 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

14181569
def 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

20012152
def 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

var/www/blueprints/hunters.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def show_tracker():
200200
tracker = Tracker.Tracker(tracker_uuid)
201201
meta = tracker.get_meta(options={'description', 'level', 'mails', 'org', 'org_name', 'filters', 'sparkline', 'tags',
202202
'filter_duplicate_notification',
203-
'user', 'webhooks', 'nb_objs', 'years'})
203+
'user', 'webhooks', 'nb_objs', 'objs_stats', 'years'})
204204

205205
if meta['type'] == 'yara':
206206
yara_rule_content = Tracker.get_yara_rule_content(meta['tracked'])
@@ -216,7 +216,14 @@ def show_tracker():
216216
if date_from:
217217
date_from, date_to = Date.sanitise_daterange(date_from, date_to)
218218
objs = tracker.get_objs_by_daterange(date_from, date_to, filter_obj_types)
219-
meta['objs'] = ail_objects.get_objects_meta(objs, options={'last_full_date', 'pdf'}, flask_context=True)
219+
meta['objs'] = []
220+
options = {'last_full_date', 'pdf'}
221+
for obj_gid in objs:
222+
obj_type, obj_subtype, obj_id = obj_gid.split(':', 2)
223+
obj_meta = ail_objects.get_object_meta(obj_type, obj_subtype, obj_id, options=options, flask_context=True)
224+
obj_meta['tracker_status'] = tracker.get_obj_status(obj_gid)
225+
obj_meta['gid'] = obj_gid
226+
meta['objs'].append(obj_meta)
220227
else:
221228
date_from = ''
222229
date_to = ''
@@ -501,6 +508,77 @@ def tracker_object_remove():
501508
else:
502509
return redirect(url_for('hunters.show_tracker', uuid=tracker_uuid))
503510

511+
@hunters.route('/tracker/object/status/done', methods=['GET'])
512+
@login_required
513+
@login_user_no_api
514+
def tracker_object_status_done():
515+
user_id = current_user.get_user_id()
516+
user_org = current_user.get_org()
517+
user_role = current_user.get_role()
518+
tracker_uuid = request.args.get('uuid')
519+
object_global_id = request.args.get('gid')
520+
521+
res = Tracker.api_tracker_object_status_done({'uuid': tracker_uuid, 'gid': object_global_id}, user_org, user_id, user_role)
522+
if res[1] != 200:
523+
return create_json_response(res[0], res[1])
524+
else:
525+
if request.referrer:
526+
return redirect(request.referrer)
527+
else:
528+
return redirect(url_for('hunters.show_tracker', uuid=tracker_uuid))
529+
530+
@hunters.route('/tracker/object/status/unread', methods=['GET'])
531+
@login_required
532+
@login_user_no_api
533+
def tracker_object_status_unread():
534+
user_id = current_user.get_user_id()
535+
user_org = current_user.get_org()
536+
user_role = current_user.get_role()
537+
tracker_uuid = request.args.get('uuid')
538+
object_global_id = request.args.get('gid')
539+
540+
res = Tracker.api_tracker_object_status_unread({'uuid': tracker_uuid, 'gid': object_global_id}, user_org, user_id, user_role)
541+
if res[1] != 200:
542+
return create_json_response(res[0], res[1])
543+
else:
544+
if request.referrer:
545+
return redirect(request.referrer)
546+
else:
547+
return redirect(url_for('hunters.show_tracker', uuid=tracker_uuid))
548+
549+
550+
@hunters.route('/tracker/object/status/reject', methods=['GET'])
551+
@login_required
552+
@login_user_no_api
553+
def tracker_object_status_reject():
554+
user_id = current_user.get_user_id()
555+
user_org = current_user.get_org()
556+
user_role = current_user.get_role()
557+
tracker_uuid = request.args.get('uuid')
558+
object_global_id = request.args.get('gid')
559+
560+
res = Tracker.api_tracker_object_status_reject({'uuid': tracker_uuid, 'gid': object_global_id}, user_org, user_id, user_role)
561+
if res[1] != 200:
562+
return create_json_response(res[0], res[1])
563+
else:
564+
if request.referrer:
565+
return redirect(request.referrer)
566+
else:
567+
return redirect(url_for('hunters.show_tracker', uuid=tracker_uuid))
568+
569+
@hunters.route('/tracker/object/status/read', methods=['POST'])
570+
@login_required
571+
@login_user_no_api
572+
def tracker_object_status_read():
573+
user_id = current_user.get_user_id()
574+
user_org = current_user.get_org()
575+
user_role = current_user.get_role()
576+
tracker_uuid = request.args.get('uuid')
577+
object_global_id = request.args.get('gid')
578+
579+
res = Tracker.api_tracker_object_status_read({'uuid': tracker_uuid, 'gid': object_global_id}, user_org, user_id, user_role)
580+
return create_json_response(res[0], res[1])
581+
504582

505583
@hunters.route('/tracker/objects', methods=['GET'])
506584
@login_required

0 commit comments

Comments
 (0)