mirror of https://github.com/CIRCL/AIL-framework
chg: [api] get object + get investigation
parent
35f0d46140
commit
e1e9609ad9
|
@ -152,25 +152,30 @@ class Investigation(object):
|
||||||
return r_tracking.smembers(f'investigations:misp:{self.uuid}')
|
return r_tracking.smembers(f'investigations:misp:{self.uuid}')
|
||||||
|
|
||||||
# # TODO: DATE FORMAT
|
# # TODO: DATE FORMAT
|
||||||
def get_metadata(self, r_str=False):
|
def get_metadata(self, options=set(), r_str=False):
|
||||||
if r_str:
|
if r_str:
|
||||||
analysis = self.get_analysis_str()
|
analysis = self.get_analysis_str()
|
||||||
threat_level = self.get_threat_level_str()
|
threat_level = self.get_threat_level_str()
|
||||||
else:
|
else:
|
||||||
analysis = self.get_analysis()
|
analysis = self.get_analysis()
|
||||||
threat_level = self.get_threat_level()
|
threat_level = self.get_threat_level()
|
||||||
return {'uuid': self.uuid,
|
|
||||||
'name': self.get_name(),
|
# 'name': self.get_name(),
|
||||||
|
meta = {'uuid': self.uuid,
|
||||||
'threat_level': threat_level,
|
'threat_level': threat_level,
|
||||||
'analysis': analysis,
|
'analysis': analysis,
|
||||||
'tags': self.get_tags(),
|
'tags': list(self.get_tags()),
|
||||||
'user_creator': self.get_creator_user(),
|
'user_creator': self.get_creator_user(),
|
||||||
'date': self.get_date(),
|
'date': self.get_date(),
|
||||||
'timestamp': self.get_timestamp(r_str=r_str),
|
'timestamp': self.get_timestamp(r_str=r_str),
|
||||||
'last_change': self.get_last_change(r_str=r_str),
|
'last_change': self.get_last_change(r_str=r_str),
|
||||||
'info': self.get_info(),
|
'info': self.get_info(),
|
||||||
'nb_objects': self.get_nb_objects(),
|
'nb_objects': self.get_nb_objects(),
|
||||||
'misp_events': self.get_misp_events()}
|
'misp_events': list(self.get_misp_events())
|
||||||
|
}
|
||||||
|
if 'objects' in options:
|
||||||
|
meta['objects'] = self.get_objects()
|
||||||
|
return meta
|
||||||
|
|
||||||
def set_name(self, name):
|
def set_name(self, name):
|
||||||
r_tracking.hset(f'investigations:data:{self.uuid}', 'name', name)
|
r_tracking.hset(f'investigations:data:{self.uuid}', 'name', name)
|
||||||
|
@ -368,6 +373,21 @@ def get_investigations_selector():
|
||||||
|
|
||||||
#### API ####
|
#### API ####
|
||||||
|
|
||||||
|
def api_get_investigation(investigation_uuid): # TODO check if is UUIDv4
|
||||||
|
investigation = Investigation(investigation_uuid)
|
||||||
|
if not investigation.exists():
|
||||||
|
return {'status': 'error', 'reason': 'Investigation Not Found'}, 404
|
||||||
|
|
||||||
|
meta = investigation.get_metadata(options={'objects'}, r_str=False)
|
||||||
|
# objs = []
|
||||||
|
# for obj in investigation.get_objects():
|
||||||
|
# obj_meta = ail_objects.get_object_meta(obj["type"], obj["subtype"], obj["id"], flask_context=True)
|
||||||
|
# comment = investigation.get_objects_comment(f'{obj["type"]}:{obj["subtype"]}:{obj["id"]}')
|
||||||
|
# if comment:
|
||||||
|
# obj_meta['comment'] = comment
|
||||||
|
# objs.append(obj_meta)
|
||||||
|
return meta, 200
|
||||||
|
|
||||||
# # TODO: CHECK Mandatory Fields
|
# # TODO: CHECK Mandatory Fields
|
||||||
# # TODO: SANITYZE Fields
|
# # TODO: SANITYZE Fields
|
||||||
# # TODO: Name ?????
|
# # TODO: Name ?????
|
||||||
|
|
|
@ -20,6 +20,8 @@ AIL_OBJECTS = sorted({'chat', 'chat-subchannel', 'chat-thread', 'cookie-name', '
|
||||||
'domain', 'etag', 'favicon', 'file-name', 'hhhash',
|
'domain', 'etag', 'favicon', 'file-name', 'hhhash',
|
||||||
'item', 'image', 'message', 'pgp', 'screenshot', 'title', 'user-account', 'username'})
|
'item', 'image', 'message', 'pgp', 'screenshot', 'title', 'user-account', 'username'})
|
||||||
|
|
||||||
|
AIL_OBJECTS_WITH_SUBTYPES = {'chat', 'chat-subchannel', 'cryptocurrency', 'pgp', 'username', 'user-account'}
|
||||||
|
|
||||||
def get_ail_uuid():
|
def get_ail_uuid():
|
||||||
ail_uuid = r_serv_db.get('ail:uuid')
|
ail_uuid = r_serv_db.get('ail:uuid')
|
||||||
if not ail_uuid:
|
if not ail_uuid:
|
||||||
|
@ -48,7 +50,7 @@ def get_all_objects():
|
||||||
return AIL_OBJECTS
|
return AIL_OBJECTS
|
||||||
|
|
||||||
def get_objects_with_subtypes():
|
def get_objects_with_subtypes():
|
||||||
return ['chat', 'cryptocurrency', 'pgp', 'username', 'user-account']
|
return AIL_OBJECTS_WITH_SUBTYPES
|
||||||
|
|
||||||
def get_object_all_subtypes(obj_type): # TODO Dynamic subtype
|
def get_object_all_subtypes(obj_type): # TODO Dynamic subtype
|
||||||
if obj_type == 'chat':
|
if obj_type == 'chat':
|
||||||
|
|
|
@ -62,8 +62,15 @@ class Favicon(AbstractDaterangeObject):
|
||||||
filename = os.path.join(FAVICON_FOLDER, self.get_rel_path())
|
filename = os.path.join(FAVICON_FOLDER, self.get_rel_path())
|
||||||
return os.path.realpath(filename)
|
return os.path.realpath(filename)
|
||||||
|
|
||||||
def get_file_content(self):
|
def get_file_content(self, r_type='str'):
|
||||||
filepath = self.get_filepath()
|
filepath = self.get_filepath()
|
||||||
|
if r_type == 'str':
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
file_content = f.read()
|
||||||
|
b64 = base64.b64encode(file_content)
|
||||||
|
# b64 = base64.encodebytes(file_content)
|
||||||
|
return b64.decode()
|
||||||
|
elif r_type == 'io':
|
||||||
with open(filepath, 'rb') as f:
|
with open(filepath, 'rb') as f:
|
||||||
file_content = BytesIO(f.read())
|
file_content = BytesIO(f.read())
|
||||||
return file_content
|
return file_content
|
||||||
|
|
|
@ -12,7 +12,7 @@ from lib.exceptions import AILObjectUnknown
|
||||||
|
|
||||||
|
|
||||||
from lib.ConfigLoader import ConfigLoader
|
from lib.ConfigLoader import ConfigLoader
|
||||||
from lib.ail_core import get_all_objects, get_object_all_subtypes
|
from lib.ail_core import get_all_objects, get_object_all_subtypes, get_objects_with_subtypes
|
||||||
from lib import correlations_engine
|
from lib import correlations_engine
|
||||||
from lib import relationships_engine
|
from lib import relationships_engine
|
||||||
from lib import btc_ail
|
from lib import btc_ail
|
||||||
|
@ -47,6 +47,9 @@ config_loader = None
|
||||||
def is_valid_object_type(obj_type):
|
def is_valid_object_type(obj_type):
|
||||||
return obj_type in get_all_objects()
|
return obj_type in get_all_objects()
|
||||||
|
|
||||||
|
def is_object_subtype(obj_type):
|
||||||
|
return obj_type in get_objects_with_subtypes()
|
||||||
|
|
||||||
def is_valid_object_subtype(obj_type, subtype):
|
def is_valid_object_subtype(obj_type, subtype):
|
||||||
return subtype in get_object_all_subtypes(obj_type)
|
return subtype in get_object_all_subtypes(obj_type)
|
||||||
|
|
||||||
|
@ -117,6 +120,39 @@ def exists_obj(obj_type, subtype, obj_id):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
#### API ####
|
||||||
|
|
||||||
|
def api_get_object(obj_type, obj_subtype, obj_id):
|
||||||
|
if not obj_id:
|
||||||
|
return {'status': 'error', 'reason': 'Invalid object id'}, 400
|
||||||
|
if not is_valid_object_type(obj_type):
|
||||||
|
return {'status': 'error', 'reason': 'Invalid object type'}, 400
|
||||||
|
if obj_subtype:
|
||||||
|
if not is_valid_object_subtype(obj_type, subtype):
|
||||||
|
return {'status': 'error', 'reason': 'Invalid object subtype'}, 400
|
||||||
|
obj = get_object(obj_type, obj_subtype, obj_id)
|
||||||
|
if not obj.exists():
|
||||||
|
return {'status': 'error', 'reason': 'Object Not Found'}, 404
|
||||||
|
options = {'chat', 'content', 'files-names', 'images', 'parent', 'parent_meta', 'reactions', 'thread', 'user-account'}
|
||||||
|
return obj.get_meta(options=options), 200
|
||||||
|
|
||||||
|
|
||||||
|
def api_get_object_type_id(obj_type, obj_id):
|
||||||
|
if not is_valid_object_type(obj_type):
|
||||||
|
return {'status': 'error', 'reason': 'Invalid object type'}, 400
|
||||||
|
if is_object_subtype(obj_type):
|
||||||
|
subtype, obj_id = obj_type.split('/', 1)
|
||||||
|
else:
|
||||||
|
subtype = None
|
||||||
|
return api_get_object(obj_type, subtype, obj_id)
|
||||||
|
|
||||||
|
|
||||||
|
def api_get_object_global_id(global_id):
|
||||||
|
obj_type, subtype, obj_id = global_id.split(':', 2)
|
||||||
|
return api_get_object(obj_type, subtype, obj_id)
|
||||||
|
|
||||||
|
#### --API-- ####
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
|
|
|
@ -234,18 +234,25 @@ def _handle_client_error(e):
|
||||||
anchor_id = anchor_id.replace('/', '_')
|
anchor_id = anchor_id.replace('/', '_')
|
||||||
api_doc_url = 'https://github.com/ail-project/ail-framework/tree/master/doc#{}'.format(anchor_id)
|
api_doc_url = 'https://github.com/ail-project/ail-framework/tree/master/doc#{}'.format(anchor_id)
|
||||||
res_dict['documentation'] = api_doc_url
|
res_dict['documentation'] = api_doc_url
|
||||||
return Response(json.dumps(res_dict, indent=2, sort_keys=True), mimetype='application/json'), 405
|
return Response(json.dumps(res_dict) + '\n', mimetype='application/json'), 405
|
||||||
else:
|
else:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def error_page_not_found(e):
|
def error_page_not_found(e):
|
||||||
if request.path.startswith('/api/'): ## # TODO: add baseUrl
|
if request.path.startswith('/api/'): ## # TODO: add baseUrl
|
||||||
return Response(json.dumps({"status": "error", "reason": "404 Not Found"}, indent=2, sort_keys=True), mimetype='application/json'), 404
|
return Response(json.dumps({"status": "error", "reason": "404 Not Found"}) + '\n', mimetype='application/json'), 404
|
||||||
else:
|
else:
|
||||||
# avoid endpoint enumeration
|
# avoid endpoint enumeration
|
||||||
return page_not_found(e)
|
return page_not_found(e)
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def _handle_client_error(e):
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
return Response(json.dumps({"status": "error", "reason": "Server Error"}) + '\n', mimetype='application/json'), 500
|
||||||
|
else:
|
||||||
|
return e
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
# avoid endpoint enumeration
|
# avoid endpoint enumeration
|
||||||
|
|
|
@ -21,14 +21,14 @@ from lib import ail_core
|
||||||
from lib import ail_updates
|
from lib import ail_updates
|
||||||
from lib import crawlers
|
from lib import crawlers
|
||||||
|
|
||||||
|
from lib import Investigations
|
||||||
from lib import Tag
|
from lib import Tag
|
||||||
|
|
||||||
from lib.objects import ail_objects
|
from lib.objects import ail_objects
|
||||||
from importer.FeederImporter import api_add_json_feeder_to_queue
|
|
||||||
|
|
||||||
from lib.objects import Domains
|
from lib.objects import Domains
|
||||||
from lib.objects import Titles
|
from lib.objects import Titles
|
||||||
|
|
||||||
|
from importer.FeederImporter import api_add_json_feeder_to_queue
|
||||||
|
|
||||||
|
|
||||||
# ============ BLUEPRINT ============
|
# ============ BLUEPRINT ============
|
||||||
|
@ -75,8 +75,8 @@ def token_required(user_role):
|
||||||
|
|
||||||
# ============ FUNCTIONS ============
|
# ============ FUNCTIONS ============
|
||||||
|
|
||||||
def create_json_response(data, status_code): # TODO REMOVE INDENT ????????????????????
|
def create_json_response(data, status_code):
|
||||||
return Response(json.dumps(data, indent=2, sort_keys=True), mimetype='application/json'), status_code
|
return Response(json.dumps(data) + "\n", mimetype='application/json'), status_code
|
||||||
|
|
||||||
# ============= ROUTES ==============
|
# ============= ROUTES ==============
|
||||||
|
|
||||||
|
@ -150,12 +150,38 @@ def import_json_item():
|
||||||
return Response(json.dumps(res[0]), mimetype='application/json'), res[1]
|
return Response(json.dumps(res[0]), mimetype='application/json'), res[1]
|
||||||
|
|
||||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
# # # # # # # # # # # # # # # OBJECTS # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # OBJECTS # # # # # # # # # # # # # # # # # # # TODO LIST OBJ TYPES + SUBTYPES
|
||||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
@api_rest.route("api/v1/object", methods=['GET']) # TODO options
|
||||||
|
@token_required('read_only')
|
||||||
|
def v1_object():
|
||||||
|
obj_gid = request.args.get('gid')
|
||||||
|
if obj_gid:
|
||||||
|
r = ail_objects.api_get_object_global_id(obj_gid)
|
||||||
|
else:
|
||||||
|
obj_type = request.args.get('type')
|
||||||
|
obj_subtype = request.args.get('subtype')
|
||||||
|
obj_id = request.args.get('id')
|
||||||
|
r = ail_objects.api_get_object(obj_type, obj_subtype, obj_id)
|
||||||
|
print(r[0])
|
||||||
|
return create_json_response(r[0], r[1])
|
||||||
|
|
||||||
|
|
||||||
|
@api_rest.route("api/v1/obj/gid/<path:object_global_id>", methods=['GET']) # TODO REMOVE ME ????
|
||||||
|
@token_required('read_only')
|
||||||
|
def v1_object_global_id(object_global_id):
|
||||||
|
r = ail_objects.api_get_object_global_id(object_global_id)
|
||||||
|
return create_json_response(r[0], r[1])
|
||||||
|
|
||||||
|
# @api_rest.route("api/v1/object/<object_type>/<object_subtype>/<path:object_id>", methods=['GET'])
|
||||||
|
@api_rest.route("api/v1/obj/<object_type>/<path:object_id>", methods=['GET']) # TODO REMOVE ME ????
|
||||||
|
@token_required('read_only')
|
||||||
|
def v1_object_type_id(object_type, object_id):
|
||||||
|
r = ail_objects.api_get_object_type_id(object_type, object_id)
|
||||||
|
return create_json_response(r[0], r[1])
|
||||||
|
|
||||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
# # # # # # # # # # # # # # # TITLES # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # TITLES # # # # # # # # # # # # # # # # # # # TODO TO REVIEW
|
||||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
|
||||||
@api_rest.route("api/v1/titles/download", methods=['GET'])
|
@api_rest.route("api/v1/titles/download", methods=['GET'])
|
||||||
|
@ -184,4 +210,13 @@ def objects_titles_download_unsafe():
|
||||||
all_titles[title_content].append(domain.get_id())
|
all_titles[title_content].append(domain.get_id())
|
||||||
return Response(json.dumps(all_titles), mimetype='application/json'), 200
|
return Response(json.dumps(all_titles), mimetype='application/json'), 200
|
||||||
|
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # INVESTIGATIONS # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||||
|
@api_rest.route("api/v1/investigation/<investigation_uuid>", methods=['GET']) # TODO options
|
||||||
|
@token_required('read_only')
|
||||||
|
def v1_investigation(investigation_uuid):
|
||||||
|
r = Investigations.api_get_investigation(investigation_uuid)
|
||||||
|
return create_json_response(r[0], r[1])
|
||||||
|
|
||||||
|
# TODO CATCH REDIRECT
|
||||||
|
|
Loading…
Reference in New Issue