first sync
Some checks failed
Deployment Verification / deploy-and-test (push) Failing after 29s

This commit is contained in:
2025-03-04 07:59:21 +01:00
parent 9cdcf486b6
commit 506716e703
1450 changed files with 577316 additions and 62 deletions

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS)
# ir@cyberactionlab.net
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# IMPORTS ------------------------------------------------
# VARS ---------------------------------------------------
# CONTENT ------------------------------------------------

View File

@@ -0,0 +1,503 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import csv
# IMPORTS ------------------------------------------------
from datetime import datetime
import marshmallow
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_assets_db import add_comment_to_asset
from app.datamgmt.case.case_assets_db import create_asset
from app.datamgmt.case.case_assets_db import delete_asset
from app.datamgmt.case.case_assets_db import delete_asset_comment
from app.datamgmt.case.case_assets_db import get_analysis_status_list
from app.datamgmt.case.case_assets_db import get_asset
from app.datamgmt.case.case_assets_db import get_asset_type_id
from app.datamgmt.case.case_assets_db import get_assets
from app.datamgmt.case.case_assets_db import get_assets_ioc_links
from app.datamgmt.case.case_assets_db import get_assets_types
from app.datamgmt.case.case_assets_db import get_case_asset_comment
from app.datamgmt.case.case_assets_db import get_case_asset_comments
from app.datamgmt.case.case_assets_db import get_case_assets_comments_count
from app.datamgmt.case.case_assets_db import get_compromise_status_list
from app.datamgmt.case.case_assets_db import get_linked_iocs_finfo_from_asset
from app.datamgmt.case.case_assets_db import get_linked_iocs_id_from_asset
from app.datamgmt.case.case_assets_db import get_similar_assets
from app.datamgmt.case.case_assets_db import set_ioc_links
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_db import get_case_client_id
from app.datamgmt.case.case_iocs_db import get_iocs
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.manage.manage_users_db import get_user_cases_fast
from app.datamgmt.states import get_assets_state
from app.datamgmt.states import update_assets_state
from app.forms import AssetBasicForm
from app.forms import ModalAddCaseAssetForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models import AnalysisStatus
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import CaseAssetsSchema
from app.schema.marshables import CommentSchema
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success
case_assets_blueprint = Blueprint('case_assets',
__name__,
template_folder='templates')
@case_assets_blueprint.route('/case/assets', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_assets(caseid, url_redir):
"""
Returns the page of case assets, with the list of available assets types.
:return: The HTML page of case assets
"""
if url_redir:
return redirect(url_for('case_assets.case_assets', cid=caseid, redirect=True))
form = ModalAddCaseAssetForm()
# Get asset types from database
form.asset_id.choices = get_assets_types()
# Retrieve the assets linked to the investigation
case = get_case(caseid)
return render_template("case_assets.html", case=case, form=form)
@case_assets_blueprint.route('/case/assets/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_list_assets(caseid):
"""
Returns the list of assets from the case.
:return: A JSON object containing the assets of the case, enhanced with assets seen on other cases.
"""
# Get all assets objects from the case and the customer id
assets = get_assets(caseid)
customer_id = get_case_client_id(caseid)
ret = {}
ret['assets'] = []
ioc_links_req = get_assets_ioc_links(caseid)
cache_ioc_link = {}
for ioc in ioc_links_req:
if ioc.asset_id not in cache_ioc_link:
cache_ioc_link[ioc.asset_id] = [ioc._asdict()]
else:
cache_ioc_link[ioc.asset_id].append(ioc._asdict())
cases_access = get_user_cases_fast(current_user.id)
for asset in assets:
asset = asset._asdict()
if len(assets) < 300:
# Find similar assets from other cases with the same customer
asset['link'] = list(get_similar_assets(
asset['asset_name'], asset['asset_type_id'], caseid, customer_id, cases_access))
else:
asset['link'] = []
asset['ioc_links'] = cache_ioc_link.get(asset['asset_id'])
ret['assets'].append(asset)
ret['state'] = get_assets_state(caseid=caseid)
return response_success("", data=ret)
@case_assets_blueprint.route('/case/assets/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_assets_state(caseid):
os = get_assets_state(caseid=caseid)
if os:
return response_success(data=os)
else:
return response_error('No assets state for this case.')
@case_assets_blueprint.route('/case/assets/add/modal', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def add_asset_modal(caseid):
form = AssetBasicForm()
form.asset_type_id.choices = get_assets_types()
form.analysis_status_id.choices = get_analysis_status_list()
form.asset_compromise_status_id.choices = get_compromise_status_list()
# Get IoCs from the case
ioc = get_iocs(caseid)
attributes = get_default_custom_attributes('asset')
return render_template("modal_add_case_multi_asset.html", form=form, asset=None, ioc=ioc, attributes=attributes)
@case_assets_blueprint.route('/case/assets/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def add_asset(caseid):
try:
# validate before saving
add_asset_schema = CaseAssetsSchema()
request_data = call_modules_hook('on_preload_asset_create', data=request.get_json(), caseid=caseid)
asset = add_asset_schema.load(request_data)
asset = create_asset(asset=asset,
caseid=caseid,
user_id=current_user.id
)
if request_data.get('ioc_links'):
errors, logs = set_ioc_links(request_data.get('ioc_links'), asset.asset_id)
if errors:
return response_error(f'Encountered errors while linking IOC. Asset has still been updated.')
asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=caseid)
if asset:
track_activity(f"added asset \"{asset.asset_name}\"", caseid=caseid)
return response_success("Asset added", data=add_asset_schema.dump(asset))
return response_error("Unable to create asset for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_assets_blueprint.route('/case/assets/upload', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_upload_ioc(caseid):
try:
# validate before saving
add_asset_schema = CaseAssetsSchema()
jsdata = request.get_json()
# get IOC list from request
csv_lines = jsdata["CSVData"].splitlines() # unavoidable since the file is passed as a string
headers = "asset_name,asset_type_name,asset_description,asset_ip,asset_domain,asset_tags"
if csv_lines[0].lower() != headers:
csv_lines.insert(0, headers)
# convert list of strings into CSV
csv_data = csv.DictReader(csv_lines, delimiter=',')
ret = []
errors = []
analysis_status = AnalysisStatus.query.filter(AnalysisStatus.name == 'Unspecified').first()
analysis_status_id = analysis_status.id
index = 0
for row in csv_data:
missing_field = False
for e in headers.split(','):
if row.get(e) is None:
errors.append(f"{e} is missing for row {index}")
missing_field = True
continue
if missing_field:
continue
# Asset name must not be empty
if not row.get("asset_name"):
errors.append(f"Empty asset name for row {index}")
track_activity(f"Attempted to upload an empty asset name")
index += 1
continue
if row.get("asset_tags"):
row["asset_tags"] = row.get("asset_tags").replace("|", ",") # Reformat Tags
if not row.get('asset_type_name'):
errors.append(f"Empty asset type for row {index}")
track_activity(f"Attempted to upload an empty asset type")
index += 1
continue
type_id = get_asset_type_id(row['asset_type_name'].lower())
if not type_id:
errors.append(f"{row.get('asset_name')} (invalid asset type: {row.get('asset_type_name')}) for row {index}")
track_activity(f"Attempted to upload unrecognized asset type \"{row.get('asset_type_name')}\"")
index += 1
continue
row['asset_type_id'] = type_id.asset_id
row.pop('asset_type_name', None)
row['analysis_status_id'] = analysis_status_id
request_data = call_modules_hook('on_preload_asset_create', data=row, caseid=caseid)
asset_sc = add_asset_schema.load(request_data)
asset_sc.custom_attributes = get_default_custom_attributes('asset')
asset = create_asset(asset=asset_sc,
caseid=caseid,
user_id=current_user.id
)
asset = call_modules_hook('on_postload_asset_create', data=asset, caseid=caseid)
if not asset:
errors.append(f"Unable to add asset for internal reason")
index += 1
continue
ret.append(request_data)
track_activity(f"added asset {asset.asset_name}", caseid=caseid)
index += 1
if len(errors) == 0:
msg = "Successfully imported data."
else:
msg = "Data is imported but we got errors with the following rows:\n- " + "\n- ".join(errors)
return response_success(msg=msg, data=ret)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_assets_blueprint.route('/case/assets/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def asset_view(cur_id, caseid):
# Get IoCs already linked to the asset
asset_iocs = get_linked_iocs_finfo_from_asset(cur_id)
ioc_prefill = [row._asdict() for row in asset_iocs]
asset = get_asset(cur_id, caseid)
if not asset:
return response_error("Invalid asset ID for this case")
asset_schema = CaseAssetsSchema()
data = asset_schema.dump(asset)
data['linked_ioc'] = ioc_prefill
return response_success(data=data)
@case_assets_blueprint.route('/case/assets/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def asset_view_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_assets.case_assets', cid=caseid, redirect=True))
# Get IoCs from the case
case_iocs = get_iocs(caseid)
# Get IoCs already linked to the asset
asset_iocs = get_linked_iocs_id_from_asset(cur_id)
ioc_prefill = [row for row in asset_iocs]
# Build the form
form = AssetBasicForm()
asset = get_asset(cur_id, caseid)
form.asset_name.render_kw = {'value': asset.asset_name}
form.asset_description.data = asset.asset_description
form.asset_info.data = asset.asset_info
form.asset_ip.render_kw = {'value': asset.asset_ip}
form.asset_domain.render_kw = {'value': asset.asset_domain}
form.asset_compromise_status_id.choices = get_compromise_status_list()
form.asset_type_id.choices = get_assets_types()
form.analysis_status_id.choices = get_analysis_status_list()
form.asset_tags.render_kw = {'value': asset.asset_tags}
comments_map = get_case_assets_comments_count([cur_id])
return render_template("modal_add_case_asset.html", form=form, asset=asset, map={}, ioc=case_iocs,
ioc_prefill=ioc_prefill, attributes=asset.custom_attributes, comments_map=comments_map)
@case_assets_blueprint.route('/case/assets/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def asset_update(cur_id, caseid):
try:
asset = get_asset(cur_id, caseid)
if not asset:
return response_error("Invalid asset ID for this case")
# validate before saving
add_asset_schema = CaseAssetsSchema()
request_data = call_modules_hook('on_preload_asset_update', data=request.get_json(), caseid=caseid)
request_data['asset_id'] = cur_id
asset_schema = add_asset_schema.load(request_data, instance=asset)
update_assets_state(caseid=caseid)
db.session.commit()
if hasattr(asset_schema, 'ioc_links'):
errors, logs = set_ioc_links(asset_schema.ioc_links, asset.asset_id)
if errors:
return response_error(f'Encountered errors while linking IOC. Asset has still been updated.')
asset_schema = call_modules_hook('on_postload_asset_update', data=asset_schema, caseid=caseid)
if asset_schema:
track_activity(f"updated asset \"{asset_schema.asset_name}\"", caseid=caseid)
return response_success("Updated asset {}".format(asset_schema.asset_name),
add_asset_schema.dump(asset_schema))
return response_error("Unable to update asset for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_assets_blueprint.route('/case/assets/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def asset_delete(cur_id, caseid):
call_modules_hook('on_preload_asset_delete', data=cur_id, caseid=caseid)
asset = get_asset(cur_id, caseid)
if not asset:
return response_error("Invalid asset ID for this case")
# Deletes an asset and the potential links with the IoCs from the database
delete_asset(cur_id, caseid)
call_modules_hook('on_postload_asset_delete', data=cur_id, caseid=caseid)
track_activity(f"removed asset ID {asset.asset_name}", caseid=caseid)
return response_success("Deleted")
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_asset_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_task.case_task', cid=caseid, redirect=True))
asset = get_asset(cur_id, caseid=caseid)
if not asset:
return response_error('Invalid asset ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='assets',
title=asset.asset_name)
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_asset_list(cur_id, caseid):
asset_comments = get_case_asset_comments(cur_id)
if asset_comments is None:
return response_error('Invalid asset ID')
# CommentSchema(many=True).dump(task_comments)
# res = [com._asdict() for com in task_comments]
return response_success(data=CommentSchema(many=True).dump(asset_comments))
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_asset_add(cur_id, caseid):
try:
asset = get_asset(cur_id, caseid=caseid)
if not asset:
return response_error('Invalid asset ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_case_id = caseid
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_comment_to_asset(asset.asset_id, comment.comment_id)
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"asset": CaseAssetsSchema().dump(asset)
}
call_modules_hook('on_postload_asset_commented', data=hook_data, caseid=caseid)
track_activity(f"asset \"{asset.asset_name}\" commented", caseid=caseid)
return response_success("Asset commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_asset_get(cur_id, com_id, caseid):
comment = get_case_asset_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=comment._asdict())
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_asset_edit(cur_id, com_id, caseid):
return case_comment_update(com_id, 'assets', caseid)
@case_assets_blueprint.route('/case/assets/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_asset_delete(cur_id, com_id, caseid):
success, msg = delete_asset_comment(cur_id, com_id, caseid)
if not success:
return response_error(msg)
call_modules_hook('on_postload_asset_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on asset {cur_id} deleted", caseid=caseid)
return response_success(msg)

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# DFIR-IRIS Team
# contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import marshmallow
from datetime import datetime
from flask import request
from app import db
from app.datamgmt.case.case_comments import get_case_comment
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.schema.marshables import CommentSchema
from app.util import response_error
from app.util import response_success
def case_comment_update(comment_id, object_type, caseid):
comment = get_case_comment(comment_id, caseid=caseid)
if not comment:
return response_error("Invalid comment ID")
try:
rq_t = request.get_json()
comment_text = rq_t.get('comment_text')
comment.comment_text = comment_text
comment.comment_update_date = datetime.utcnow()
comment_schema = CommentSchema()
db.session.commit()
hook = object_type
if hook.endswith('s'):
hook = hook[:-1]
call_modules_hook(f'on_postload_{hook}_comment_update', data=comment_schema.dump(comment), caseid=caseid)
track_activity(f"comment {comment.comment_id} on {object_type} edited", caseid=caseid)
return response_success("Comment edited", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import itertools
from datetime import datetime
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import url_for
from flask_login import current_user
from flask_wtf import FlaskForm
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_events_db import get_case_events_assets_graph
from app.datamgmt.case.case_events_db import get_case_events_ioc_graph
from app.models.authorization import CaseAccessLevel
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_success
case_graph_blueprint = Blueprint('case_graph',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@case_graph_blueprint.route('/case/graph', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_graph(caseid, url_redir):
if url_redir:
return redirect(url_for('case_graph.case_graph', cid=caseid, redirect=True))
case = get_case(caseid)
form = FlaskForm()
return render_template("case_graph.html", case=case, form=form)
@case_graph_blueprint.route('/case/graph/getdata', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_graph_get_data(caseid):
events = get_case_events_assets_graph(caseid)
events.extend(get_case_events_ioc_graph(caseid))
nodes = []
edges = []
dates = {
"human": [],
"machine": []
}
tmp = {}
for event in events:
if hasattr(event, 'asset_compromise_status_id'):
if event.asset_compromise_status_id == 1:
img = event.asset_icon_compromised
else:
img = event.asset_icon_not_compromised
if event.asset_ip:
title = "{} -{}".format(event.asset_ip, event.asset_description)
else:
title = "{}".format(event.asset_description)
label = event.asset_name
idx = f'a{event.asset_id}'
node_type = 'asset'
else:
img = 'virus-covid-solid.png'
label = event.ioc_value
title = event.ioc_description
idx = f'b{event.ioc_id}'
node_type = 'ioc'
try:
date = "{}-{}-{}".format(event.event_date.day, event.event_date.month, event.event_date.year)
except:
date = '15-05-2021'
if date not in dates:
dates['human'].append(date)
dates['machine'].append(datetime.timestamp(event.event_date))
new_node = {
'id': idx,
'label': label,
'image': '/static/assets/img/graph/' + img,
'shape': 'image',
'title': title,
'value': 1
}
if current_user.in_dark_mode:
new_node['font'] = "12px verdana white"
if not any(node['id'] == idx for node in nodes):
nodes.append(new_node)
ak = {
'node_id': idx,
'node_title': "{} - {}".format(event.event_date, event.event_title),
'node_name': label,
'node_type': node_type
}
if tmp.get(event.event_id):
tmp[event.event_id]['list'].append(ak)
else:
tmp[event.event_id] = {
'master_node': [],
'list': [ak]
}
for event_id in tmp:
for subset in itertools.combinations(tmp[event_id]['list'], 2):
if subset[0]['node_type'] == 'ioc' and subset[1]['node_type'] == 'ioc' and len(tmp[event_id]['list']) != 2:
continue
edge = {
'from': subset[0]['node_id'],
'to': subset[1]['node_id'],
'title': subset[0]['node_title'],
'dashes': subset[0]['node_type'] == 'ioc' or subset[1]['node_type'] == 'ioc'
}
edges.append(edge)
resp = {
'nodes': nodes,
'edges': edges,
'dates': dates
}
return response_success("", data=resp)

View File

@@ -0,0 +1,453 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# IMPORTS ------------------------------------------------
from datetime import datetime
import csv
import logging as log
import marshmallow
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_assets_db import get_assets_types
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_iocs_db import add_comment_to_ioc
from app.datamgmt.case.case_iocs_db import add_ioc
from app.datamgmt.case.case_iocs_db import add_ioc_link
from app.datamgmt.case.case_iocs_db import check_ioc_type_id
from app.datamgmt.case.case_iocs_db import delete_ioc
from app.datamgmt.case.case_iocs_db import delete_ioc_comment
from app.datamgmt.case.case_iocs_db import get_case_ioc_comment
from app.datamgmt.case.case_iocs_db import get_case_ioc_comments
from app.datamgmt.case.case_iocs_db import get_case_iocs_comments_count
from app.datamgmt.case.case_iocs_db import get_detailed_iocs
from app.datamgmt.case.case_iocs_db import get_ioc
from app.datamgmt.case.case_iocs_db import get_ioc_links
from app.datamgmt.case.case_iocs_db import get_ioc_type_id
from app.datamgmt.case.case_iocs_db import get_ioc_types_list
from app.datamgmt.case.case_iocs_db import get_tlps
from app.datamgmt.case.case_iocs_db import get_tlps_dict
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import get_ioc_state
from app.datamgmt.states import update_ioc_state
from app.forms import ModalAddCaseAssetForm
from app.forms import ModalAddCaseIOCForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
from app.models.models import Ioc
from app.schema.marshables import CommentSchema
from app.schema.marshables import IocSchema
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success
case_ioc_blueprint = Blueprint(
'case_ioc',
__name__,
template_folder='templates'
)
# CONTENT ------------------------------------------------
@case_ioc_blueprint.route('/case/ioc', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_ioc(caseid, url_redir):
if url_redir:
return redirect(url_for('case_ioc.case_ioc', cid=caseid, redirect=True))
form = ModalAddCaseAssetForm()
form.asset_id.choices = get_assets_types()
# Retrieve the assets linked to the investigation
case = get_case(caseid)
return render_template("case_ioc.html", case=case, form=form)
@case_ioc_blueprint.route('/case/ioc/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_list_ioc(caseid):
iocs = get_detailed_iocs(caseid)
ret = {}
ret['ioc'] = []
for ioc in iocs:
out = ioc._asdict()
# Get links of the IoCs seen in other cases
ial = get_ioc_links(ioc.ioc_id, caseid)
out['link'] = [row._asdict() for row in ial]
# Legacy, must be changed next version
out['misp_link'] = None
ret['ioc'].append(out)
ret['state'] = get_ioc_state(caseid=caseid)
return response_success("", data=ret)
@case_ioc_blueprint.route('/case/ioc/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_ioc_state(caseid):
os = get_ioc_state(caseid=caseid)
if os:
return response_success(data=os)
else:
return response_error('No IOC state for this case.')
@case_ioc_blueprint.route('/case/ioc/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_ioc(caseid):
try:
# validate before saving
add_ioc_schema = IocSchema()
request_data = call_modules_hook('on_preload_ioc_create', data=request.get_json(), caseid=caseid)
ioc = add_ioc_schema.load(request_data)
if not check_ioc_type_id(type_id=ioc.ioc_type_id):
return response_error("Not a valid IOC type")
ioc, existed = add_ioc(ioc=ioc,
user_id=current_user.id,
caseid=caseid
)
link_existed = add_ioc_link(ioc.ioc_id, caseid)
if link_existed:
return response_success("IOC already exists and linked to this case", data=add_ioc_schema.dump(ioc))
if not link_existed:
ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=caseid)
if ioc:
track_activity("added ioc \"{}\"".format(ioc.ioc_value), caseid=caseid)
msg = "IOC already existed in DB. Updated with info on DB." if existed else "IOC added"
return response_success(msg=msg, data=add_ioc_schema.dump(ioc))
return response_error("Unable to create IOC for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_ioc_blueprint.route('/case/ioc/upload', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_upload_ioc(caseid):
try:
# validate before saving
add_ioc_schema = IocSchema()
jsdata = request.get_json()
# get IOC list from request
headers = "ioc_value,ioc_type,ioc_description,ioc_tags,ioc_tlp"
csv_lines = jsdata["CSVData"].splitlines() # unavoidable since the file is passed as a string
if csv_lines[0].lower() != headers:
csv_lines.insert(0, headers)
# convert list of strings into CSV
csv_data = csv.DictReader(csv_lines, quotechar='"', delimiter=',')
# build a Dict of possible TLP
tlp_dict = get_tlps_dict()
ret = []
errors = []
index = 0
for row in csv_data:
for e in headers.split(','):
if row.get(e) is None:
errors.append(f"{e} is missing for row {index}")
index += 1
continue
# IOC value must not be empty
if not row.get("ioc_value"):
errors.append(f"Empty IOC value for row {index}")
track_activity(f"Attempted to upload an empty IOC value")
index += 1
continue
row["ioc_tags"] = row["ioc_tags"].replace("|", ",") # Reformat Tags
# Convert TLP into TLP id
if row["ioc_tlp"] in tlp_dict:
row["ioc_tlp_id"] = tlp_dict[row["ioc_tlp"]]
else:
row["ioc_tlp_id"] = ""
row.pop("ioc_tlp", None)
type_id = get_ioc_type_id(row['ioc_type'].lower())
if not type_id:
errors.append(f"{row['ioc_value']} (invalid ioc type: {row['ioc_type']}) for row {index}")
log.error(f'Unrecognised IOC type {row["ioc_type"]}')
index += 1
continue
row['ioc_type_id'] = type_id.type_id
row.pop('ioc_type', None)
request_data = call_modules_hook('on_preload_ioc_create', data=row, caseid=caseid)
ioc = add_ioc_schema.load(request_data)
ioc.custom_attributes = get_default_custom_attributes('ioc')
ioc, existed = add_ioc(ioc=ioc,
user_id=current_user.id,
caseid=caseid
)
link_existed = add_ioc_link(ioc.ioc_id, caseid)
if link_existed:
errors.append(f"{ioc.ioc_value} (already exists and linked to this case)")
log.error(f"IOC {ioc.ioc_value} already exists and linked to this case")
index += 1
continue
if ioc:
ioc = call_modules_hook('on_postload_ioc_create', data=ioc, caseid=caseid)
ret.append(request_data)
track_activity(f"added ioc \"{ioc.ioc_value}\"", caseid=caseid)
else:
errors.append(f"{ioc.ioc_value} (internal reasons)")
log.error(f"Unable to create IOC {ioc.ioc_value} for internal reasons")
index += 1
if len(errors) == 0:
msg = "Successfully imported data."
else:
msg = "Data is imported but we got errors with the following rows:\n- " + "\n- ".join(errors)
return response_success(msg=msg, data=ret)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_ioc_blueprint.route('/case/ioc/add/modal', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_ioc_modal(caseid):
form = ModalAddCaseIOCForm()
form.ioc_type_id.choices = [(row['type_id'], row['type_name']) for row in get_ioc_types_list()]
form.ioc_tlp_id.choices = get_tlps()
attributes = get_default_custom_attributes('ioc')
return render_template("modal_add_case_ioc.html", form=form, ioc=Ioc(), attributes=attributes)
@case_ioc_blueprint.route('/case/ioc/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_delete_ioc(cur_id, caseid):
call_modules_hook('on_preload_ioc_delete', data=cur_id, caseid=caseid)
ioc = get_ioc(cur_id, caseid)
if not ioc:
return response_error('Not a valid IOC for this case')
if not delete_ioc(ioc, caseid):
track_activity(f"unlinked IOC ID {ioc.ioc_value}", caseid=caseid)
return response_success(f"IOC {cur_id} unlinked")
call_modules_hook('on_postload_ioc_delete', data=cur_id, caseid=caseid)
track_activity(f"deleted IOC \"{ioc.ioc_value}\"", caseid=caseid)
return response_success(f"IOC {cur_id} deleted")
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_view_ioc_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_assets.case_assets', cid=caseid, redirect=True))
form = ModalAddCaseIOCForm()
ioc = get_ioc(cur_id, caseid)
if not ioc:
return response_error("Invalid IOC ID for this case")
form.ioc_type_id.choices = [(row['type_id'], row['type_name']) for row in get_ioc_types_list()]
form.ioc_tlp_id.choices = get_tlps()
# Render the IOC
form.ioc_tags.render_kw = {'value': ioc.ioc_tags}
form.ioc_description.data = ioc.ioc_description
form.ioc_value.data = ioc.ioc_value
comments_map = get_case_iocs_comments_count([cur_id])
return render_template("modal_add_case_ioc.html", form=form, ioc=ioc, attributes=ioc.custom_attributes,
comments_map=comments_map)
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_view_ioc(cur_id, caseid):
ioc_schema = IocSchema()
ioc = get_ioc(cur_id, caseid)
if not ioc:
return response_error("Invalid IOC ID for this case")
return response_success(data=ioc_schema.dump(ioc))
@case_ioc_blueprint.route('/case/ioc/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_update_ioc(cur_id, caseid):
try:
ioc = get_ioc(cur_id, caseid)
if not ioc:
return response_error("Invalid IOC ID for this case")
request_data = call_modules_hook('on_preload_ioc_update', data=request.get_json(), caseid=caseid)
# validate before saving
ioc_schema = IocSchema()
request_data['ioc_id'] = cur_id
ioc_sc = ioc_schema.load(request_data, instance=ioc)
ioc_sc.user_id = current_user.id
if not check_ioc_type_id(type_id=ioc_sc.ioc_type_id):
return response_error("Not a valid IOC type")
update_ioc_state(caseid=caseid)
db.session.commit()
ioc_sc = call_modules_hook('on_postload_ioc_update', data=ioc_sc, caseid=caseid)
if ioc_sc:
track_activity(f"updated ioc \"{ioc_sc.ioc_value}\"", caseid=caseid)
return response_success(f"Updated ioc \"{ioc_sc.ioc_value}\"", data=ioc_schema.dump(ioc))
return response_error("Unable to update ioc for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_ioc_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_ioc.case_ioc', cid=caseid, redirect=True))
ioc = get_ioc(cur_id, caseid=caseid)
if not ioc:
return response_error('Invalid ioc ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='ioc',
title=ioc.ioc_value)
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_ioc_list(cur_id, caseid):
ioc_comments = get_case_ioc_comments(cur_id)
if ioc_comments is None:
return response_error('Invalid ioc ID')
return response_success(data=CommentSchema(many=True).dump(ioc_comments))
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_ioc_add(cur_id, caseid):
try:
ioc = get_ioc(cur_id, caseid=caseid)
if not ioc:
return response_error('Invalid ioc ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_case_id = caseid
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_comment_to_ioc(ioc.ioc_id, comment.comment_id)
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"ioc": IocSchema().dump(ioc)
}
call_modules_hook('on_postload_ioc_commented', data=hook_data, caseid=caseid)
track_activity(f"ioc \"{ioc.ioc_value}\" commented", caseid=caseid)
return response_success("Event commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_ioc_get(cur_id, com_id, caseid):
comment = get_case_ioc_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=comment._asdict())
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_ioc_edit(cur_id, com_id, caseid):
return case_comment_update(com_id, 'ioc', caseid)
@case_ioc_blueprint.route('/case/ioc/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_ioc_delete(cur_id, com_id, caseid):
success, msg = delete_ioc_comment(cur_id, com_id)
if not success:
return response_error(msg)
call_modules_hook('on_postload_ioc_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on ioc {cur_id} deleted", caseid=caseid)
return response_success(msg)

View File

@@ -0,0 +1,511 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import marshmallow
# IMPORTS ------------------------------------------------
from datetime import datetime
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_socketio import emit, join_room, leave_room
from flask_wtf import FlaskForm
from app import db, socket_io
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_db import case_get_desc_crc
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_notes_db import add_comment_to_note
from app.datamgmt.case.case_notes_db import add_note
from app.datamgmt.case.case_notes_db import add_note_group
from app.datamgmt.case.case_notes_db import delete_note
from app.datamgmt.case.case_notes_db import delete_note_comment
from app.datamgmt.case.case_notes_db import delete_note_group
from app.datamgmt.case.case_notes_db import find_pattern_in_notes
from app.datamgmt.case.case_notes_db import get_case_note_comment
from app.datamgmt.case.case_notes_db import get_case_note_comments
from app.datamgmt.case.case_notes_db import get_case_notes_comments_count
from app.datamgmt.case.case_notes_db import get_group_details
from app.datamgmt.case.case_notes_db import get_groups_short
from app.datamgmt.case.case_notes_db import get_note
from app.datamgmt.case.case_notes_db import get_notes_from_group
from app.datamgmt.case.case_notes_db import update_note
from app.datamgmt.case.case_notes_db import update_note_group
from app.datamgmt.states import get_notes_state
from app.forms import CaseNoteForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import CaseAddNoteSchema
from app.schema.marshables import CaseGroupNoteSchema
from app.schema.marshables import CaseNoteSchema
from app.schema.marshables import CommentSchema
from app.util import ac_api_case_requires, ac_socket_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success
case_notes_blueprint = Blueprint('case_notes',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@case_notes_blueprint.route('/case/notes', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_notes(caseid, url_redir):
if url_redir:
return redirect(url_for('case_notes.case_notes', cid=caseid, redirect=True))
form = FlaskForm()
case = get_case(caseid)
if case:
crc32, desc = case_get_desc_crc(caseid)
else:
crc32 = None
desc = None
return render_template('case_notes.html', case=case, form=form, th_desc=desc, crc=crc32)
@case_notes_blueprint.route('/case/notes/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_note_detail(cur_id, caseid):
try:
note = get_note(cur_id, caseid=caseid)
if not note:
return response_error(msg="Invalid note ID")
note_schema = CaseNoteSchema()
return response_success(data=note_schema.dump(note))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_notes_blueprint.route('/case/notes/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_note_detail_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_notes.case_notes', cid=caseid, redirect=True))
form = CaseNoteForm()
note = get_note(cur_id, caseid)
ca = None
if note:
form.content = note.note_content
form.title = note.note_title
form.note_title.render_kw = {"value": note.note_title}
setattr(form, 'note_id', note.note_id)
setattr(form, 'note_uuid', note.note_uuid)
ca = note.custom_attributes
comments_map = get_case_notes_comments_count([cur_id])
return render_template("modal_note_edit.html", note=form, id=cur_id, attributes=ca,
ncid=note.note_case_id, comments_map=comments_map)
return response_error(f'Unable to find note ID {cur_id} for case {caseid}')
@case_notes_blueprint.route('/case/notes/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_note_delete(cur_id, caseid):
call_modules_hook('on_preload_note_delete', data=cur_id, caseid=caseid)
note = get_note(cur_id, caseid)
if not note:
return response_error("Invalid note ID for this case")
try:
delete_note(cur_id, caseid)
except Exception as e:
return response_error("Unable to remove note", data=e.__traceback__)
call_modules_hook('on_postload_note_delete', data=cur_id, caseid=caseid)
track_activity(f"deleted note \"{note.note_title}\"", caseid=caseid)
return response_success(f"Note deleted {cur_id}")
@case_notes_blueprint.route('/case/notes/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_note_save(cur_id, caseid):
try:
# validate before saving
addnote_schema = CaseAddNoteSchema()
request_data = call_modules_hook('on_preload_note_update', data=request.get_json(), caseid=caseid)
request_data['note_id'] = cur_id
addnote_schema.load(request_data, partial=['group_id'])
note = update_note(note_content=request_data.get('note_content'),
note_title=request_data.get('note_title'),
update_date=datetime.utcnow(),
user_id=current_user.id,
note_id=cur_id,
caseid=caseid
)
if not note:
return response_error("Invalid note ID for this case")
note = call_modules_hook('on_postload_note_update', data=note, caseid=caseid)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
track_activity(f"updated note \"{note.note_title}\"", caseid=caseid)
return response_success(f"Note ID {cur_id} saved", data=addnote_schema.dump(note))
@case_notes_blueprint.route('/case/notes/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_note_add(caseid):
try:
# validate before saving
addnote_schema = CaseAddNoteSchema()
request_data = call_modules_hook('on_preload_note_create', data=request.get_json(), caseid=caseid)
addnote_schema.verify_group_id(request_data, caseid=caseid)
addnote_schema.load(request_data)
note = add_note(request_data.get('note_title'),
datetime.utcnow(),
current_user.id,
caseid,
request_data.get('group_id'),
note_content=request_data.get('note_content'))
note = call_modules_hook('on_postload_note_create', data=note, caseid=caseid)
if note:
casenote_schema = CaseNoteSchema()
track_activity(f"added note \"{note.note_title}\"", caseid=caseid)
return response_success('Note added', data=casenote_schema.dump(note))
return response_error("Unable to create note for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_notes_blueprint.route('/case/notes/groups/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_load_notes_groups(caseid):
if not get_case(caseid=caseid):
return response_error("Invalid case ID")
groups_short = get_groups_short(caseid)
sta = []
for group in groups_short:
notes = get_notes_from_group(caseid=caseid, group_id=group.group_id)
group_d = group._asdict()
group_d['notes'] = [note._asdict() for note in notes]
sta.append(group_d)
sta = sorted(sta, key=lambda i: i['group_id'])
ret = {
'groups': sta,
'state': get_notes_state(caseid=caseid)
}
return response_success("", data=ret)
@case_notes_blueprint.route('/case/notes/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_notes_state(caseid):
os = get_notes_state(caseid=caseid)
if os:
return response_success(data=os)
else:
return response_error('No notes state for this case.')
@case_notes_blueprint.route('/case/notes/search', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_search_notes(caseid):
if request.is_json:
search = request.json.get('search_term')
ns = []
if search:
search = "%{}%".format(search)
ns = find_pattern_in_notes(search, caseid)
ns = [row._asdict() for row in ns]
return response_success("", data=ns)
return response_error("Invalid request")
@case_notes_blueprint.route('/case/notes/groups/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_notes_groups(caseid):
title = ''
if request.is_json:
title = request.json.get('group_title') or ''
ng = add_note_group(group_title=title,
caseid=caseid,
userid=current_user.id,
creationdate=datetime.utcnow())
if ng.group_id:
group_schema = CaseGroupNoteSchema()
track_activity(f"added group note \"{ng.group_title}\"", caseid=caseid)
return response_success("Notes group added", data=group_schema.dump(ng))
else:
return response_error("Unable to add a new group")
return response_error("Invalid request")
@case_notes_blueprint.route('/case/notes/groups/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_delete_notes_groups(cur_id, caseid):
if not delete_note_group(cur_id, caseid):
return response_error("Invalid group ID")
track_activity("deleted group note ID {}".format(cur_id), caseid=caseid)
return response_success("Group ID {} deleted".format(cur_id))
@case_notes_blueprint.route('/case/notes/groups/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_notes_group(cur_id, caseid):
group = get_group_details(cur_id, caseid)
if not group:
return response_error(f"Group ID {cur_id} not found")
return response_success("", data=group)
@case_notes_blueprint.route('/case/notes/groups/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_edit_notes_groups(cur_id, caseid):
js_data = request.get_json()
if not js_data:
return response_error("Invalid data")
group_title = js_data.get('group_title')
if not group_title:
return response_error("Missing field group_title")
ng = update_note_group(group_title, cur_id, caseid)
if ng:
# Note group has been properly found and updated in db
track_activity("updated group note \"{}\"".format(group_title), caseid=caseid)
group_schema = CaseGroupNoteSchema()
return response_success("Updated title of group ID {}".format(cur_id), data=group_schema.dump(ng))
return response_error("Group ID {} not found".format(cur_id))
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_note_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_note.case_note', cid=caseid, redirect=True))
note = get_note(cur_id, caseid=caseid)
if not note:
return response_error('Invalid note ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='notes',
title=note.note_title)
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_note_list(cur_id, caseid):
note_comments = get_case_note_comments(cur_id)
if note_comments is None:
return response_error('Invalid note ID')
return response_success(data=CommentSchema(many=True).dump(note_comments))
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_note_add(cur_id, caseid):
try:
note = get_note(cur_id, caseid=caseid)
if not note:
return response_error('Invalid note ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_case_id = caseid
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_comment_to_note(note.note_id, comment.comment_id)
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"note": CaseNoteSchema().dump(note)
}
call_modules_hook('on_postload_note_commented', data=hook_data, caseid=caseid)
track_activity("note \"{}\" commented".format(note.note_title), caseid=caseid)
return response_success("Event commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_note_get(cur_id, com_id, caseid):
comment = get_case_note_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=comment._asdict())
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_note_edit(cur_id, com_id, caseid):
return case_comment_update(com_id, 'notes', caseid)
@case_notes_blueprint.route('/case/notes/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_note_delete(cur_id, com_id, caseid):
success, msg = delete_note_comment(cur_id, com_id)
if not success:
return response_error(msg)
call_modules_hook('on_postload_note_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on note {cur_id} deleted", caseid=caseid)
return response_success(msg)
@socket_io.on('change-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_change_note(data):
data['last_change'] = current_user.user
emit('change-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel'])
@socket_io.on('save-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_save_note(data):
data['last_saved'] = current_user.user
emit('save-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel'])
@socket_io.on('clear_buffer-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_clear_buffer_note(message):
emit('clear_buffer-note', message, room=message['channel'])
@socket_io.on('join-notes')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_join_note(data):
room = data['channel']
join_room(room=room)
emit('join-notes', {
'message': f"{current_user.user} just joined",
"user": current_user.user
}, room=room)
@socket_io.on('ping-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_ping_note(data):
emit('ping-note', {"user": current_user.name, "note_id": data['note_id']}, room=data['channel'])
@socket_io.on('pong-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_ping_note(data):
emit('pong-note', {"user": current_user.name, "note_id": data['note_id']}, room=data['channel'])
@socket_io.on('overview-map-note')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_overview_map_note(data):
emit('overview-map-note', {"user": current_user.user, "note_id": data['note_id']}, room=data['channel'])
@socket_io.on('join-notes-overview')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_join_overview(data):
room = data['channel']
join_room(room=room)
emit('join-notes-overview', {
'message': f"{current_user.user} just joined",
"user": current_user.user
}, room=room)
@socket_io.on('disconnect')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_disconnect(data):
emit('disconnect', current_user.user, broadcast=True)

View File

@@ -0,0 +1,306 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# IMPORTS ------------------------------------------------
from datetime import datetime
import marshmallow
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_wtf import FlaskForm
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_rfiles_db import add_comment_to_evidence
from app.datamgmt.case.case_rfiles_db import add_rfile
from app.datamgmt.case.case_rfiles_db import delete_evidence_comment
from app.datamgmt.case.case_rfiles_db import delete_rfile
from app.datamgmt.case.case_rfiles_db import get_case_evidence_comment
from app.datamgmt.case.case_rfiles_db import get_case_evidence_comments
from app.datamgmt.case.case_rfiles_db import get_case_evidence_comments_count
from app.datamgmt.case.case_rfiles_db import get_rfile
from app.datamgmt.case.case_rfiles_db import get_rfiles
from app.datamgmt.case.case_rfiles_db import update_rfile
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import get_evidences_state
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
from app.schema.marshables import CaseEvidenceSchema
from app.schema.marshables import CommentSchema
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success
case_rfiles_blueprint = Blueprint(
'case_rfiles',
__name__,
template_folder='templates'
)
# CONTENT ------------------------------------------------
@case_rfiles_blueprint.route('/case/evidences', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_rfile(caseid, url_redir):
if url_redir:
return redirect(url_for('case_rfiles.case_rfile', cid=caseid, redirect=True))
form = FlaskForm()
case = get_case(caseid)
return render_template("case_rfile.html", case=case, form=form)
@case_rfiles_blueprint.route('/case/evidences/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_list_rfiles(caseid):
crf = get_rfiles(caseid)
ret = {
"evidences": [row._asdict() for row in crf],
"state": get_evidences_state(caseid=caseid)
}
return response_success("", data=ret)
@case_rfiles_blueprint.route('/case/evidences/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_rfiles_state(caseid):
os = get_evidences_state(caseid=caseid)
if os:
return response_success(data=os)
else:
return response_error('No evidences state for this case.')
@case_rfiles_blueprint.route('/case/evidences/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_rfile(caseid):
try:
# validate before saving
evidence_schema = CaseEvidenceSchema()
request_data = call_modules_hook('on_preload_evidence_create', data=request.get_json(), caseid=caseid)
evidence = evidence_schema.load(request_data)
crf = add_rfile(evidence=evidence,
user_id=current_user.id,
caseid=caseid
)
crf = call_modules_hook('on_postload_evidence_create', data=crf, caseid=caseid)
if crf:
track_activity(f"added evidence \"{crf.filename}\"", caseid=caseid)
return response_success("Evidence added", data=evidence_schema.dump(crf))
return response_error("Unable to create task for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_evidence(cur_id, caseid):
crf = get_rfile(cur_id, caseid)
if not crf:
return response_error("Invalid evidence ID for this case")
evidence_schema = CaseEvidenceSchema()
return response_success(data=evidence_schema.dump(crf))
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_edit_rfile_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_rfiles.case_rfile', cid=caseid, redirect=True))
crf = get_rfile(cur_id, caseid)
if not crf:
return response_error("Invalid evidence ID for this case")
comments_map = get_case_evidence_comments_count([cur_id])
return render_template("modal_add_case_rfile.html", rfile=crf, attributes=crf.custom_attributes,
comments_map=comments_map)
@case_rfiles_blueprint.route('/case/evidences/add/modal', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_rfile_modal(caseid):
return render_template("modal_add_case_rfile.html", rfile=None, attributes=get_default_custom_attributes('evidence'))
@case_rfiles_blueprint.route('/case/evidences/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_edit_rfile(cur_id, caseid):
try:
# validate before saving
evidence_schema = CaseEvidenceSchema()
request_data = call_modules_hook('on_preload_evidence_update', data=request.get_json(), caseid=caseid)
crf = get_rfile(cur_id, caseid)
if not crf:
return response_error("Invalid evidence ID for this case")
request_data['id'] = cur_id
evidence = evidence_schema.load(request_data, instance=crf)
evd = update_rfile(evidence=evidence,
user_id=current_user.id,
caseid=caseid
)
evd = call_modules_hook('on_postload_evidence_update', data=evd, caseid=caseid)
if evd:
track_activity(f"updated evidence \"{evd.filename}\"", caseid=caseid)
return response_success("Evidence {} updated".format(evd.filename), data=evidence_schema.dump(evd))
return response_error("Unable to update task for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_rfiles_blueprint.route('/case/evidences/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_delete_rfile(cur_id, caseid):
call_modules_hook('on_preload_evidence_delete', data=cur_id, caseid=caseid)
crf = get_rfile(cur_id, caseid)
if not crf:
return response_error("Invalid evidence ID for this case")
delete_rfile(cur_id, caseid=caseid)
call_modules_hook('on_postload_evidence_delete', data=cur_id, caseid=caseid)
track_activity(f"deleted evidence \"{crf.filename}\" from registry", caseid)
return response_success("Evidence deleted")
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_evidence_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_task.case_task', cid=caseid, redirect=True))
evidence = get_rfile(cur_id, caseid=caseid)
if not evidence:
return response_error('Invalid evidence ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='evidences',
title=evidence.filename)
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_evidence_list(cur_id, caseid):
evidence_comments = get_case_evidence_comments(cur_id)
if evidence_comments is None:
return response_error('Invalid evidence ID')
return response_success(data=CommentSchema(many=True).dump(evidence_comments))
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_evidence_add(cur_id, caseid):
try:
evidence = get_rfile(cur_id, caseid=caseid)
if not evidence:
return response_error('Invalid evidence ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_case_id = caseid
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_comment_to_evidence(evidence.id, comment.comment_id)
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"evidence": CaseEvidenceSchema().dump(evidence)
}
call_modules_hook('on_postload_evidence_commented', data=hook_data, caseid=caseid)
track_activity(f"evidence \"{evidence.filename}\" commented", caseid=caseid)
return response_success("Event commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_evidence_get(cur_id, com_id, caseid):
comment = get_case_evidence_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=comment._asdict())
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_evidence_edit(cur_id, com_id, caseid):
return case_comment_update(com_id, 'tasks', caseid)
@case_rfiles_blueprint.route('/case/evidences/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_evidence_delete(cur_id, com_id, caseid):
success, msg = delete_evidence_comment(cur_id, com_id)
if not success:
return response_error(msg)
call_modules_hook('on_postload_evidence_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on evidence {cur_id} deleted", caseid=caseid)
return response_success(msg)

View File

@@ -0,0 +1,449 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import binascii
import marshmallow
# IMPORTS ------------------------------------------------
import traceback
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_socketio import emit
from flask_socketio import join_room
from flask_wtf import FlaskForm
from sqlalchemy import and_
from sqlalchemy import desc
from app import app
from app import db
from app import socket_io
from app.blueprints.case.case_assets_routes import case_assets_blueprint
from app.blueprints.case.case_graphs_routes import case_graph_blueprint
from app.blueprints.case.case_ioc_routes import case_ioc_blueprint
from app.blueprints.case.case_notes_routes import case_notes_blueprint
from app.blueprints.case.case_rfiles_routes import case_rfiles_blueprint
from app.blueprints.case.case_tasks_routes import case_tasks_blueprint
from app.blueprints.case.case_timeline_routes import case_timeline_blueprint
from app.datamgmt.case.case_db import case_exists, get_review_id_from_name
from app.datamgmt.case.case_db import case_get_desc_crc
from app.datamgmt.case.case_db import get_activities_report_template
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_db import get_case_report_template
from app.datamgmt.case.case_db import get_case_tags
from app.datamgmt.manage.manage_groups_db import add_case_access_to_group
from app.datamgmt.manage.manage_groups_db import get_group_with_members
from app.datamgmt.manage.manage_groups_db import get_groups_list
from app.datamgmt.manage.manage_users_db import get_user
from app.datamgmt.manage.manage_users_db import get_users_list_restricted_from_case
from app.datamgmt.manage.manage_users_db import set_user_case_access
from app.datamgmt.reporter.report_db import export_case_json
from app.forms import PipelinesCaseForm
from app.iris_engine.access_control.utils import ac_get_all_access_level, ac_fast_check_current_user_has_case_access, \
ac_fast_check_user_has_case_access
from app.iris_engine.access_control.utils import ac_set_case_access_for_users
from app.iris_engine.module_handler.module_handler import list_available_pipelines
from app.iris_engine.utils.tracker import track_activity
from app.models import CaseStatus, ReviewStatusList
from app.models import UserActivity
from app.models.authorization import CaseAccessLevel
from app.models.authorization import User
from app.schema.marshables import TaskLogSchema, CaseSchema
from app.util import ac_api_case_requires, add_obj_history_entry
from app.util import ac_case_requires
from app.util import ac_socket_requires
from app.util import response_error
from app.util import response_success
app.register_blueprint(case_timeline_blueprint)
app.register_blueprint(case_notes_blueprint)
app.register_blueprint(case_assets_blueprint)
app.register_blueprint(case_ioc_blueprint)
app.register_blueprint(case_rfiles_blueprint)
app.register_blueprint(case_graph_blueprint)
app.register_blueprint(case_tasks_blueprint)
case_blueprint = Blueprint('case',
__name__,
template_folder='templates')
event_tags = ["Network", "Server", "ActiveDirectory", "Computer", "Malware", "User Interaction"]
log = app.logger
# CONTENT ------------------------------------------------
@case_blueprint.route('/case', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_r(caseid, url_redir):
if url_redir:
return redirect(url_for('case.case_r', cid=caseid, redirect=True))
case = get_case(caseid)
setattr(case, 'case_tags', get_case_tags(caseid))
form = FlaskForm()
reports = get_case_report_template()
reports = [row for row in reports]
reports_act = get_activities_report_template()
reports_act = [row for row in reports_act]
if not case:
return render_template('select_case.html')
desc_crc32, description = case_get_desc_crc(caseid)
setattr(case, 'status_name', CaseStatus(case.status_id).name.replace('_', ' ').title())
return render_template('case.html', case=case, desc=description, crc=desc_crc32,
reports=reports, reports_act=reports_act, form=form)
@case_blueprint.route('/case/exists', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_exists_r(caseid):
if case_exists(caseid):
return response_success('Case exists')
else:
return response_error('Case does not exist', 404)
@case_blueprint.route('/case/pipelines-modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.full_access)
def case_pipelines_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('case.case_r', cid=caseid, redirect=True))
case = get_case(caseid)
form = PipelinesCaseForm()
pl = list_available_pipelines()
form.pipeline.choices = [("{}-{}".format(ap[0], ap[1]['pipeline_internal_name']),
ap[1]['pipeline_human_name'])for ap in pl]
# Return default page of case management
pipeline_args = [("{}-{}".format(ap[0], ap[1]['pipeline_internal_name']),
ap[1]['pipeline_human_name'], ap[1]['pipeline_args'])for ap in pl]
return render_template('modal_case_pipelines.html', case=case, form=form, pipeline_args=pipeline_args)
@socket_io.on('change')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_summary_onchange(data):
data['last_change'] = current_user.user
emit('change', data, to=data['channel'], skip_sid=request.sid)
@socket_io.on('save')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_summary_onsave(data):
data['last_saved'] = current_user.user
emit('save', data, to=data['channel'], skip_sid=request.sid)
@socket_io.on('clear_buffer')
@ac_socket_requires(CaseAccessLevel.full_access)
def socket_summary_onchange(message):
emit('clear_buffer', message)
@socket_io.on('join')
@ac_socket_requires(CaseAccessLevel.full_access)
def get_message(data):
room = data['channel']
join_room(room=room)
emit('join', {'message': f"{current_user.user} just joined"}, room=room)
@case_blueprint.route('/case/summary/update', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def desc_fetch(caseid):
js_data = request.get_json()
case = get_case(caseid)
if not case:
return response_error('Invalid case ID')
case.description = js_data.get('case_description')
crc = binascii.crc32(case.description.encode('utf-8'))
db.session.commit()
track_activity("updated summary", caseid)
if not request.cookies.get('session'):
# API call so we propagate the message to everyone
data = {
"case_description": case.description,
"last_saved": current_user.user
}
socket_io.emit('save', data, to=f"case-{caseid}")
return response_success("Summary updated", data=crc)
@case_blueprint.route('/case/summary/fetch', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def summary_fetch(caseid):
desc_crc32, description = case_get_desc_crc(caseid)
return response_success("Summary fetch", data={'case_description': description, 'crc32': desc_crc32})
@case_blueprint.route('/case/activities/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def activity_fetch(caseid):
ua = UserActivity.query.with_entities(
UserActivity.activity_date,
User.name,
UserActivity.activity_desc,
UserActivity.is_from_api
).filter(and_(
UserActivity.case_id == caseid,
UserActivity.display_in_ui == True
)).join(
UserActivity.user
).order_by(
desc(UserActivity.activity_date)
).limit(40).all()
output = [a._asdict() for a in ua]
return response_success("", data=output)
@case_blueprint.route("/case/export", methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def export_case(caseid):
return response_success('', data=export_case_json(caseid))
@case_blueprint.route('/case/tasklog/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_tasklog(caseid):
log_schema = TaskLogSchema()
try:
log_data = log_schema.load(request.get_json())
ua = track_activity(log_data.get('log_content'), caseid, user_input=True)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_success("Log saved", data=ua)
@case_blueprint.route('/case/users/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_users(caseid):
users = get_users_list_restricted_from_case(caseid)
return response_success(data=users)
@case_blueprint.route('/case/groups/access/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.full_access)
def groups_cac_view(caseid, url_redir):
if url_redir:
return redirect(url_for('case.case_r', cid=caseid, redirect=True))
groups = get_groups_list()
access_levels = ac_get_all_access_level()
return render_template('modal_cac_to_groups.html', groups=groups, access_levels=access_levels, caseid=caseid)
@case_blueprint.route('/case/access/set-group', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def group_cac_set_case(caseid):
data = request.get_json()
if not data:
return response_error("Invalid request")
if data.get('case_id') != caseid:
return response_error("Inconsistent case ID")
case = get_case(caseid)
if not case:
return response_error("Invalid case ID")
group_id = data.get('group_id')
access_level = data.get('access_level')
group = get_group_with_members(group_id)
try:
success, logs = add_case_access_to_group(group, [data.get('case_id')], access_level)
if success:
success, logs = ac_set_case_access_for_users(group.group_members, caseid, access_level)
except Exception as e:
log.error("Error while setting case access for group: {}".format(e))
log.error(traceback.format_exc())
return response_error(msg=str(e))
if success:
track_activity("case access set to {} for group {}".format(data.get('access_level'), group_id), caseid)
add_obj_history_entry(case, "access changed to {} for group {}".format(data.get('access_level'), group_id),
commit=True)
return response_success(msg=logs)
return response_error(msg=logs)
@case_blueprint.route('/case/access/set-user', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def user_cac_set_case(caseid):
data = request.get_json()
if not data:
return response_error("Invalid request")
if data.get('user_id') == current_user.id:
return response_error("I can't let you do that, Dave")
user = get_user(data.get('user_id'))
if not user:
return response_error("Invalid user ID")
if data.get('case_id') != caseid:
return response_error("Inconsistent case ID")
case = get_case(caseid)
if not case:
return response_error('Invalid case ID')
try:
success, logs = set_user_case_access(user.id, data.get('case_id'), data.get('access_level'))
track_activity("case access set to {} for user {}".format(data.get('access_level'), user.name), caseid)
add_obj_history_entry(case, "access changed to {} for user {}".format(data.get('access_level'), user.name))
db.session.commit()
except Exception as e:
log.error("Error while setting case access for user: {}".format(e))
log.error(traceback.format_exc())
return response_error(msg=str(e))
if success:
return response_success(msg=logs)
return response_error(msg=logs)
@case_blueprint.route('/case/update-status', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_update_status(caseid):
case = get_case(caseid)
if not case:
return response_error('Invalid case ID')
status = request.get_json().get('status_id')
case_status = set(item.value for item in CaseStatus)
try:
status = int(status)
except ValueError:
return response_error('Invalid status')
except TypeError:
return response_error('Invalid status. Expected int')
if status not in case_status:
return response_error('Invalid status')
case.status_id = status
add_obj_history_entry(case, f'status updated to {CaseStatus(status).name}')
db.session.commit()
return response_success("Case status updated", data=case.status_id)
@case_blueprint.route('/case/md-helper', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_md_helper(caseid, url_redir):
return render_template('case_md_helper.html')
@case_blueprint.route('/case/review/update', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_review(caseid):
case = get_case(caseid)
if not case:
return response_error('Invalid case ID')
action = request.get_json().get('action')
reviewer_id = request.get_json().get('reviewer_id')
if action == 'start':
review_name = ReviewStatusList.review_in_progress
elif action == 'cancel' or action == 'request':
review_name = ReviewStatusList.pending_review
elif action == 'no_review':
review_name = ReviewStatusList.no_review_required
elif action == 'to_review':
review_name = ReviewStatusList.not_reviewed
elif action == 'done':
review_name = ReviewStatusList.reviewed
else:
return response_error('Invalid action')
case.review_status_id = get_review_id_from_name(review_name)
if reviewer_id:
try:
reviewer_id = int(reviewer_id)
except ValueError:
return response_error('Invalid reviewer ID')
if not ac_fast_check_user_has_case_access(reviewer_id, caseid, [CaseAccessLevel.full_access]):
return response_error('Invalid reviewer ID')
case.reviewer_id = reviewer_id
db.session.commit()
add_obj_history_entry(case, f'review status updated to {review_name}')
track_activity(f'review status updated to {review_name}', caseid)
db.session.commit()
return response_success("Case review updated", data=CaseSchema().dump(case))

View File

@@ -0,0 +1,368 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2021 - Airbus CyberSecurity (SAS) - DFIR-IRIS Team
# ir@cyberactionlab.net - contact@dfir-iris.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# IMPORTS ------------------------------------------------
from datetime import datetime
import marshmallow
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from flask_wtf import FlaskForm
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_db import get_case
from app.datamgmt.case.case_tasks_db import add_comment_to_task
from app.datamgmt.case.case_tasks_db import add_task
from app.datamgmt.case.case_tasks_db import delete_task
from app.datamgmt.case.case_tasks_db import delete_task_comment
from app.datamgmt.case.case_tasks_db import get_case_task_comment
from app.datamgmt.case.case_tasks_db import get_case_task_comments
from app.datamgmt.case.case_tasks_db import get_case_tasks_comments_count
from app.datamgmt.case.case_tasks_db import get_task
from app.datamgmt.case.case_tasks_db import get_task_with_assignees
from app.datamgmt.case.case_tasks_db import get_tasks_status
from app.datamgmt.case.case_tasks_db import get_tasks_with_assignees
from app.datamgmt.case.case_tasks_db import update_task_assignees
from app.datamgmt.case.case_tasks_db import update_task_status
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.states import get_tasks_state
from app.datamgmt.states import update_tasks_state
from app.forms import CaseTaskForm
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import CaseAccessLevel
from app.models.authorization import User
from app.models.models import CaseTasks
from app.schema.marshables import CaseTaskSchema
from app.schema.marshables import CommentSchema
from app.util import ac_api_case_requires
from app.util import ac_case_requires
from app.util import response_error
from app.util import response_success
case_tasks_blueprint = Blueprint('case_tasks',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@case_tasks_blueprint.route('/case/tasks', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_tasks(caseid, url_redir):
if url_redir:
return redirect(url_for('case_tasks.case_tasks', cid=caseid, redirect=True))
form = FlaskForm()
case = get_case(caseid)
return render_template("case_tasks.html", case=case, form=form)
@case_tasks_blueprint.route('/case/tasks/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_tasks(caseid):
ct = get_tasks_with_assignees(caseid)
if not ct:
output = []
else:
output = ct
ret = {
"tasks_status": get_tasks_status(),
"tasks": output,
"state": get_tasks_state(caseid=caseid)
}
return response_success("", data=ret)
@case_tasks_blueprint.route('/case/tasks/state', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_get_tasks_state(caseid):
os = get_tasks_state(caseid=caseid)
if os:
return response_success(data=os)
else:
return response_error('No tasks state for this case.')
@case_tasks_blueprint.route('/case/tasks/status/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_task_statusupdate(cur_id, caseid):
task = get_task(task_id=cur_id, caseid=caseid)
if not task:
return response_error("Invalid task ID for this case")
if request.is_json:
if update_task_status(request.json.get('task_status_id'), cur_id, caseid):
task_schema = CaseTaskSchema()
return response_success("Task status updated", data=task_schema.dump(task))
else:
return response_error("Invalid status")
else:
return response_error("Invalid request")
@case_tasks_blueprint.route('/case/tasks/add/modal', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_task_modal(caseid):
task = CaseTasks()
task.custom_attributes = get_default_custom_attributes('task')
form = CaseTaskForm()
form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
form.task_assignees_id.choices = []
return render_template("modal_add_case_task.html", form=form, task=task, uid=current_user.id, user_name=None,
attributes=task.custom_attributes)
@case_tasks_blueprint.route('/case/tasks/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_add_task(caseid):
try:
# validate before saving
task_schema = CaseTaskSchema()
request_data = call_modules_hook('on_preload_task_create', data=request.get_json(), caseid=caseid)
if 'task_assignee_id' in request_data or 'task_assignees_id' not in request_data:
return response_error('task_assignee_id is not valid anymore since v1.5.0')
task_assignee_list = request_data['task_assignees_id']
del request_data['task_assignees_id']
task = task_schema.load(request_data)
ctask = add_task(task=task,
assignee_id_list=task_assignee_list,
user_id=current_user.id,
caseid=caseid
)
ctask = call_modules_hook('on_postload_task_create', data=ctask, caseid=caseid)
if ctask:
track_activity(f"added task \"{ctask.task_title}\"", caseid=caseid)
return response_success("Task '{}' added".format(ctask.task_title), data=task_schema.dump(ctask))
return response_error("Unable to create task for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_task_view(cur_id, caseid):
task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
if not task:
return response_error("Invalid task ID for this case")
task_schema = CaseTaskSchema()
return response_success(data=task_schema.dump(task))
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_task_view_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_tasks.case_tasks', cid=caseid, redirect=True))
form = CaseTaskForm()
task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
form.task_status_id.choices = [(a.id, a.status_name) for a in get_tasks_status()]
form.task_assignees_id.choices = []
if not task:
return response_error("Invalid task ID for this case")
form.task_title.render_kw = {'value': task.task_title}
form.task_description.data = task.task_description
user_name, = User.query.with_entities(User.name).filter(User.id == task.task_userid_update).first()
comments_map = get_case_tasks_comments_count([task.id])
return render_template("modal_add_case_task.html", form=form, task=task, user_name=user_name,
comments_map=comments_map, attributes=task.custom_attributes)
@case_tasks_blueprint.route('/case/tasks/update/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_edit_task(cur_id, caseid):
try:
task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
if not task:
return response_error("Invalid task ID for this case")
request_data = call_modules_hook('on_preload_task_update', data=request.get_json(), caseid=caseid)
if 'task_assignee_id' in request_data or 'task_assignees_id' not in request_data:
return response_error('task_assignee_id is not valid anymore since v1.5.0')
# validate before saving
task_assignee_list = request_data['task_assignees_id']
del request_data['task_assignees_id']
task_schema = CaseTaskSchema()
request_data['id'] = cur_id
task = task_schema.load(request_data, instance=task)
task.task_userid_update = current_user.id
task.task_last_update = datetime.utcnow()
update_task_assignees(task, task_assignee_list, caseid)
update_tasks_state(caseid=caseid)
db.session.commit()
task = call_modules_hook('on_postload_task_update', data=task, caseid=caseid)
if task:
track_activity(f"updated task \"{task.task_title}\" (status {task.task_status_id})",
caseid=caseid)
return response_success("Task '{}' updated".format(task.task_title), data=task_schema.dump(task))
return response_error("Unable to update task for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@case_tasks_blueprint.route('/case/tasks/delete/<int:cur_id>', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_delete_task(cur_id, caseid):
call_modules_hook('on_preload_task_delete', data=cur_id, caseid=caseid)
task = get_task_with_assignees(task_id=cur_id, case_id=caseid)
if not task:
return response_error("Invalid task ID for this case")
delete_task(task.id)
update_tasks_state(caseid=caseid)
call_modules_hook('on_postload_task_delete', data=cur_id, caseid=caseid)
track_activity(f"deleted task \"{task.task_title}\"")
return response_success("Task deleted")
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/modal', methods=['GET'])
@ac_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('case_task.case_task', cid=caseid, redirect=True))
task = get_task(cur_id, caseid=caseid)
if not task:
return response_error('Invalid task ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='tasks',
title=task.task_title)
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_list(cur_id, caseid):
task_comments = get_case_task_comments(cur_id)
if task_comments is None:
return response_error('Invalid task ID')
return response_success(data=CommentSchema(many=True).dump(task_comments))
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/add', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_add(cur_id, caseid):
try:
task = get_task(cur_id, caseid=caseid)
if not task:
return response_error('Invalid task ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_case_id = caseid
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_comment_to_task(task.id, comment.comment_id)
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"task": CaseTaskSchema().dump(task)
}
call_modules_hook('on_postload_task_commented', data=hook_data, caseid=caseid)
track_activity(f"task \"{task.task_title}\" commented", caseid=caseid)
return response_success("Task commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_comment_task_get(cur_id, com_id, caseid):
comment = get_case_task_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=comment._asdict())
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_edit(cur_id, com_id, caseid):
return case_comment_update(com_id, 'tasks', caseid)
@case_tasks_blueprint.route('/case/tasks/<int:cur_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_case_requires(CaseAccessLevel.full_access)
def case_comment_task_delete(cur_id, com_id, caseid):
success, msg = delete_task_comment(cur_id, com_id)
if not success:
return response_error(msg)
call_modules_hook('on_postload_task_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on task {cur_id} deleted", caseid=caseid)
return response_success(msg)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
<nav class="nav-bottom rounded" id="h_nav_tab">
<div class="container-fluid" >
<ul class="navbar-nav rounded" style="background-color:#4c72a130;">
<li class="nav-item {{ active if page == 'case' }}">
<a class="nav-link" href="/case?cid={{session['current_case'].case_id}}">
<span class="menu-title">Summary</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/notes?cid={{session['current_case'].case_id}}">
<span class="menu-title">Notes</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/assets?cid={{session['current_case'].case_id}}">
<span class="menu-title">Assets</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/ioc?cid={{session['current_case'].case_id}}">
<span class="menu-title">IOC</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/timeline?cid={{session['current_case'].case_id}}">
<span class="menu-title">Timeline</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/graph?cid={{session['current_case'].case_id}}">
<span class="menu-title">Graph</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/tasks?cid={{session['current_case'].case_id}}">
<span class="menu-title">Tasks</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/case/evidences?cid={{session['current_case'].case_id}}">
<span class="menu-title">Evidences</span>
</a>
</li>
</ul>
</div>
</nav>

View File

@@ -0,0 +1,51 @@
<div class="secondary-header">
<div class="nav-bottom ">
<div class="container-fluid float-left">
<div class="row">
<div class="col col-md-ml-12 col-sm-12">
<ul class="nav page-navigation page-navigation-style-2 page-navigation-primary">
<li class="nav-item {{ active if page == 'case' }}">
<button class="btn btn-sm btn-light" href="#" onclick="case_detail('{{ case.case_id }}');">
<i class="fa-solid fa-gear mr-1"></i>
<span class="">Manage</span>
</button>
</li>
<li class="nav-item">
<div class="dropdown">
<button class="btn btn-sm btn-light float-left ml-2" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fa-solid fa-bolt mr-1"></i> Processors</span>
</button>
<div class="dropdown-menu pull-right" id="case_modal_quick_actions" aria-labelledby="dropdownMenuButton">
</div>
</div>
</li>
<li class="nav-item">
<button class="btn btn-sm btn-light float-left ml-2" id="case_pipeline" onclick="case_pipeline_popup();">
<span aria-hidden="true"><i class="fa-solid fa-upload mr-1"></i>Pipelines</span>
</button>
</li>
<li class="nav-item ml-auto">
{% if case.review_status.status_name == "Not reviewed" or not case.review_status %}
<button class="btn btn-sm btn-light float-left ml-2" id="request_review">
<span aria-hidden="true"><i class="fa-solid fa-clipboard-check mr-1"></i>Request review</span>
</button>
{% endif %}
<btn href="#" onclick="report_template_selector();" class="btn btn-dark btn-sm float-right ml-2">
<span class="btn-label">
<i class="fa fa-file-arrow-down"></i>
</span>
Generate report
</btn>
<btn href="#" onclick="act_report_template_selector();" class="btn btn-sm btn-dark float-right ml-2">
<span class="btn-label">
<i class="fa fa-chart-line"></i>
</span>
Activity report
</btn>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,337 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case summary {% endblock title %}
{% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/select2.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-multiselect.min.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if case.close_date %}
<div class="panel-header bg-close-gradient">
{% else %}
<div class="panel-header bg-info-gradient">
{% endif %}
<div class="page-inner py-5">
<div class="d-flex align-items-left align-items-md-center flex-column flex-md-row mt--3">
<div class="col">
<div class="row">
<h2 class="text-white pb-2 fw-bold case-name"> <i class="icon-big flaticon-network mr-2"></i> {{ case.name|unquote }}
</h2>
</div>
<h5 class="text-white op-7 mb-1"><b>Open on</b> {{ case.open_date }} by {{ case.user.name }}</h5>
<h5 class="text-white op-7 mb-3"><b>Owned by</b> {{ case.owner.name }}</h5>
{% if case.close_date %}
<h5 class="text-warning mb-1">Closed on {{ case.close_date }}</h5>
{% endif %}
</div>
<div class="col mt-4">
<div class="row">
<span title="Case outcome" class="float-right btn btn-rounded badge-pill hidden-caret ml-auto btn-xs mr-2 mb-2 {% if case.status_id == 1%}badge-success{% elif case.status_id == 2 %}badge-danger{% else %}btn-light{% endif %}"
onclick="case_detail('{{ case.case_id }}', true);"
><i class="fa-solid fa-group-arrows-rotate mr-2"></i>{{ case.status_name }}</span>
</div>
<div class="row">
<div class="ml-auto">
<div class="row">
<h5 class="text-white op-7 mb-2 float-right mr-4"><b>Customer</b> : {{ case.client.name }}</h5>
</div>
<div class="row">
{% if case.soc_id %} <h5 class="text-white op-7 mb-2 mr-4"><b>SOC ID :</b> {{ case.soc_id }}</h5> {% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="row mt-2 mb--2">
<div class="ml-2 col">
<div class="row ml-1">
{% if case.state %}<h5 title="Case state" onclick="case_detail('{{ case.case_id }}', true);" style="cursor:pointer;"><span class="btn-rounded badge-pill hidden-caret btn-sm btn-light"><i class="fa-solid fa-business-time mr-1"></i> {{ case.state.state_name }}</span></h5>{% endif %}
{% if case.classification %}<h5 title="Classification" onclick="case_detail('{{ case.case_id }}', true);" style="cursor:pointer;"><span class="btn-rounded badge-pill hidden-caret btn-sm btn-light ml-2"><i class="fa-solid fa-shield-virus mr-1"></i>{{ case.classification.name_expanded }}</span></h5>{% endif %}
{% if case.alerts| length > 0 %}<h5 title="Alerts"><a class="btn-rounded badge-pill hidden-caret btn-sm btn-dark ml-2 badge-warning" href="/alerts?cid={{ case.case_id }}&sort=desc&case_id={{ case.case_id }}" target="_blank" rel="noopener"><i class="fa-solid fa-bell mr-1"></i> {{ case.alerts| length }} related alerts</a></h5>{% endif %}
{% if case.review_status.status_name == "Reviewed" %}
<h5 title="Reviewed"> <a class="text-white btn-rounded badge-pill hidden-caret btn-sm ml-2 badge-success"><i class="fa-regular fa-circle-check mr-2"></i>Case reviewed by {% if case.reviewer.id == current_user.id %} you {% else %} {{ case.reviewer.name }} {% endif %}</a></h5>
{% endif %}
</div>
</div>
<div class="col mr-2">
{% if case.case_tags %}
{% for tag in case.case_tags %}
<span class="badge badge-pill badge-light ml-1 pull-right"><i class="fa fa-tag mr-1"></i> {{ tag }}</span>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
<div class="page-inner mt--5">
<div class="row row-card-no-pd" style="padding-top: 0px;padding-bottom: 3px;">
{% include 'case-nav_landing.html' %}
</div>
<div id="caseReviewState" data-review-state="{{ case.review_status.status_name }}" data-reviewer-id="{{ case.reviewer_id }}" data-reviewer-name="{{ case.reviewer.name }}" style="display: none;"></div>
{% if case.reviewer_id == current_user.id and case.review_status.status_name != "Reviewed" and case.review_status.status_name != "Not reviewed" %}
<div class="row row-card-no-pd review-card mt--3 mb--3 bg-warning-gradient" style="display: none;">
<div class="col-md-12">
<h4 class="font-weight-bold"><i class="fa-solid fa-triangle-exclamation text-danger ml-2 mr-2"></i>Review requested
<button class="btn btn-sm float-right btn-dark mr-3 mt-2 btn-start-review">Start review</button>
<button class="btn btn-sm float-right btn-success mr-3 mt-2 btn-confirm-review" style="display:none">Confirm review</button>
<button class="btn btn-sm float-right btn-light mr-3 mt-2 btn-cancel-review" style="display:none">Cancel review</button></h4>
<span class="ml-2" id="reviewSubtitle">You have been requested to review this case.</span>
</div>
</div>
{% elif case.review_status.status_name == "Review in progress" %}
<div class="row row-card-no-pd mt--3 mb--3 bg-warning-gradient">
<div class="col-md-12">
<h4 class="font-weight-bold mt-1"><i class="fa-solid fa-list-check ml-2 mr-2"></i>Review by {{ case.reviewer.name }} in progress</h4>
</div>
</div>
{% elif case.review_status.status_name == "Pending review" %}
<div class="row row-card-no-pd mt--3 mb--3 bg-warning-gradient">
<div class="col-md-12">
<h4 class="font-weight-bold mt-1"><i class="fa-solid fa-triangle-exclamation text-danger ml-2 mr-2"></i>Review by {{ case.reviewer.name }} pending</h4>
</div>
</div>
{% endif %}
<div class="row row-card-no-pd">
<div class="col-md-12">
<div class="card mb-4" id="rescard1">
<div class="card-header">
<div class="row">
{{ form.hidden_tag() }}
<a href="#case_summary_card" class="d-block nav-link mr-auto" data-toggle="collapse" aria-expanded="true" aria-controls="case_summary_card">
<h4 class="m-0 font-weight-bold">Case summary {{ "(Syncing with DB )" if case.id }}</h4>
</a>
<div class="mr-0 float-right">
<small id="content_typing" class="mr-3 mt-1"></small>
<small id="content_last_saved_by" class="mr-3 mt-1"></small>
<span id="last_saved" class="badge mr-3 ml-2"></span>
<small id="content_last_sync"></small>
<button class="btn btn-sm mr-2 ml-3" onclick="edit_case_summary();" id="sum_edit_btn" >Edit</button>
<button type="button" id="sum_refresh_btn" class="btn btn-sm btn-outline-default mr-3" onclick="sync_editor();">
Refresh
</button>
</div>
</div>
</div>
<div class="collapsed" id="case_summary_card">
<div class="card-body">
<div class="row mb-1">
<div class="col" id="summary_edition_btn" style="display:none;">
<div class="btn btn-sm btn-light mr-1 " title="CTRL-B" onclick="editor.insertSnippet('**${1:$SELECTION}**');editor.focus();"><i class="fa-solid fa-bold"></i></div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-I" onclick="editor.insertSnippet('*${1:$SELECTION}*');editor.focus();"><i class="fa-solid fa-italic"></i></div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-1" onclick="editor.insertSnippet('# ${1:$SELECTION}');editor.focus();">H1</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-2" onclick="editor.insertSnippet('## ${1:$SELECTION}');editor.focus();">H2</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-3" onclick="editor.insertSnippet('### ${1:$SELECTION}');editor.focus();">H3</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-4" onclick="editor.insertSnippet('#### ${1:$SELECTION}');editor.focus();">H4</div>
<div class="btn btn-sm btn-light mr-1" title="Insert code" onclick="editor.insertSnippet('```${1:$SELECTION}```');editor.focus();"><i class="fa-solid fa-code"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert link" onclick="editor.insertSnippet('[${1:$SELECTION}](url)');editor.focus();"><i class="fa-solid fa-link"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert table" onclick="editor.insertSnippet('|\t|\t|\t|\n|--|--|--|\n|\t|\t|\t|\n|\t|\t|\t|');editor.focus();"><i class="fa-solid fa-table"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert bullet list" onclick="editor.insertSnippet('\n- \n- \n- ');editor.focus();"><i class="fa-solid fa-list"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert numbered list" onclick="editor.insertSnippet('\n1. a \n2. b \n3. c ');editor.focus();"><i class="fa-solid fa-list-ol"></i></div>
</div>
</div>
<div class="row">
<div class="col-md-6" id="container_editor_summary">
<div style="display: none" id="fetched_crc"></div>
<div id="editor_summary" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}"></div>
<textarea id="case_summary" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-md-6" id="ctrd_casesum">
<div id="targetDiv"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_select_report" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5>Select report template</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
{% if reports| length == 0 %}
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">No report template found</h4>
<p>Report templates are configured in <a href="/manage/templates?cid={{case.case_id}}">the management section</a>.</p>
</div>
{% else %}
<div class="col">
<p>Since IRIS v2.0.0, the report generation supports images. Integration of images might fail depending on the situation.<br/><code>Safe Mode</code> can be used to generate the report without them.</p>
</div>
<select class="selectpicker form-control bg-outline-success dropdown-submenu" data-show-subtext="true" data-live-search="true" id="select_report">
{% for report in reports %}
<option data-toggle="tooltip" value="{{ report[0] }}" data-subtext="{{ report[3] }}">{{ report[1] }} ({{ report[2].capitalize() }})</option>
{% endfor %}
</select>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-light float-left mt-2 mr-auto" onclick="gen_report(true);">
<span class="btn-label">
<i class="fa fa-file-download"></i>
</span>
Generate in Safe Mode
</a>
<a href="#" class="btn btn-light float-right mt-2 ml-2" onclick="gen_report(false);">
<span class="btn-label">
<i class="fa fa-file-download"></i>
</span>
Generate
</a>
{% endif %}
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_select_report_act" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5>Select activity report template</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
{% if reports| length == 0 %}
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">No report template found</h4>
<p>Report templates are configured in <a href="/manage/templates?cid={{case.case_id}}">the management section</a>.</p>
</div>
{% else %}
<div class="col">
<p>Since IRIS v2.0.0, the report generation supports images. Integration of images might fail depending on the situation.<br/><code>Safe Mode</code> can be used to generate the report without them.</p>
</div>
<select class="selectpicker form-control bg-outline-success dropdown-submenu mb-2" data-show-subtext="true" data-live-search="true" id="select_report_act">
{% for report in reports_act %}
<option data-toggle="tooltip" value="{{ report[0] }}" data-subtext="{{ report[3] }}">{{ report[1] }} ({{ report[2].capitalize() }})</option>
{% endfor %}
</select>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-light float-left mt-2 mr-auto" onclick="gen_act_report(true);">
<span class="btn-label">
<i class="fa fa-file-download"></i>
</span>
Generate in Safe Mode
</a>
<a href="#" class="btn btn-light float-right mt-2 ml-2" onclick="gen_act_report(false);">
<span class="btn-label">
<i class="fa fa-file-download"></i>
</span>
Generate
</a>
{% endif %}
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_choose_reviewer" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<form method="post" action="" id="form_choose_reviewer">
<div class="modal-header">
<h5>Choose reviewer</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="row mb-2">
<div class="col-12">
<div class="form-group">
<select class="selectpicker form-control" data-dropup-auto="false" data-live-search="true" id="reviewer_id">
</select>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 d-flex">
<button type="button" class="btn btn-default mr-auto" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-success ml-auto" id="submit_set_reviewer">Request</button>
</div>
</div>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_case_detail" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="info_case_modal_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal bg-shadow-gradient" tabindex="-1" role="dialog" id="modal_ac_additional" data-backdrop="true">
</div>
<div class="modal bg-shadow-gradient" tabindex="-1" role="dialog" id="modal_case_review" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Case Review</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12">
<p>Do you confirm that the case has been reviewed?</p>
</div>
</div>
<div class="row">
<div class="col-12">
<button type="button" class="btn btn-danger float-left" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success float-right" id="confirmReview">Confirm Review</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'includes/footer.html' %}
{% endif %} {% endblock content %} {% block javascripts %}
<script src="/static/assets/js/plugin/ace/src-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/assets/js/plugin/ace/src-noconflict/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/assets/js/core/socket.io.js"></script>
<script src="/static/assets/js/plugin/select/select2.js"></script>
<script src="/static/assets/js/plugin/showdown/showdown.min.js"></script>
<script src="/static/assets/js/iris/crc32.js"></script>
<script src="/static/assets/js/iris/datatablesUtils.js"></script>
<script src="/static/assets/js/iris/case.js"></script>
<script src="/static/assets/js/iris/manage.cases.common.js"></script>
<script src="/static/assets/js/iris/case.summary.js"></script>
<script src="/static/assets/js/plugin/select/bootstrap-select.min.js"></script>
<script src="/static/assets/js/plugin/select/bootstrap-multiselect.min.js"></script>
<script>
$('#modal_select_report').selectpicker();
load_menu_mod_options_modal([{{case.case_id}}], 'case', $("#case_modal_quick_actions"));
</script>
{% endblock javascripts %}

View File

@@ -0,0 +1,148 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case Assets {% endblock title %} {% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<nav class="navbar navbar-header navbar-expand-lg pt-2 pb-2 bg-primary-gradient">
<div class="container-fluid">
<div class="collapse" id="search-nav">
<div id="tables_button"></div>
</div>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
<span class="text-warning text-sm mr-2" id="page_warning"></span>
</li>
<li class="nav-item">
<button class="btn btn-primary btn-sm" onclick="reload_assets();">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item">
<button class="btn btn-dark btn-sm" onclick="add_assets();">
<span class="menu-title">Add assets</span>
</button>
</li>
<li class="nav-item">
<div class="dropdown">
<button class="btn btn-sm btn-border btn-black" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="menu-title"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick="fire_upload_assets();">Upload CSV of assets</a>
</div>
</div>
</li>
</ul>
</div>
</nav>
<div class="page-inner ">
<div class="row ">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="col-md-12">
<div class="card" id="card_main_load" style="display:none;">
<div class="card-body">
<div class="col" id="assets_table_wrapper">
<table class="table display table-striped table-hover responsive" width="100%" cellspacing="0" id="assets_table" >
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>IP</th>
<th>Compromised</th>
<th>IOC</th>
<th>Tags</th>
<th>Analysis</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>IP</th>
<th>Compromised</th>
<th>IOC</th>
<th>Tags</th>
<th>Analysis</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal modal-case-focus" tabindex="-1" role="dialog" id="modal_add_asset" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_add_asset_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endif %}
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_upload_assets" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<form method="post" action="" id="form_upload_assets">
<div class="modal-content">
<div class="modal-header">
<h5>Upload assets list (CSV format)</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="ioc_format" class="placeholder">Expected CSV File format</label>
<textarea class="form-control col-md-12 col-sm-12 sizable-textarea" rows="2" disabled>asset_name,asset_type_name,asset_description,asset_ip,asset_domain,asset_tags (separated with &quot;|&quot;)</textarea>
</div>
<div class="form-group">
<label class="placeholder">CSV File format example</label>
<textarea class="form-control col-md-12 col-sm-12 sizable-textarea" rows="3" disabled>asset_name,asset_type_name,asset_description,asset_ip,asset_domain,asset_tags
"My computer","Mac - Computer","Computer of Mme Michu","192.168.15.5","iris.local","Compta|Mac"
"XCAS","Windows - Server","Xcas server","192.168.15.48","iris.local",""</textarea>
</div>
<div class="form-group">
<label class="placeholder">Choose CSV file to import : </label>
<input id="input_upload_assets" type="file" accept="text/csv">
</div>
</div>
<div class='invalid-feedback' id='ioc-invalid-msg'></div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark mr-auto" onclick="generate_sample_csv();">Download sample CSV</button>
<button type="button" class="btn btn-outline-success" onclick="upload_assets();">Upload</button>
</div>
</div><!-- /.modal-content -->
</form>
</div><!-- /.modal-dialog -->
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/plugin/sortable/sortable.js "></script>
<script src="/static/assets/js/iris/case.asset.js"></script>
{% endblock javascripts %}

View File

@@ -0,0 +1,71 @@
{% extends "layouts/default_ext.html" %}
{% block title %} Case Graph {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-slider.min.css">
<link rel="stylesheet" href="/static/assets/css/select2.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link href="/static/assets/css/vis.min.css" rel="stylesheet" type="text/css" />
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
<nav class="navbar navbar-header navbar-expand-lg bg-primary-gradient">
<ul class="container-fluid mt-3 mb--2">
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item hidden-caret">
<button class="btn btn-primary btn-sm" onclick="get_case_graph();">
<span class="menu-title">Refresh</span>
</button>
</li>
</ul>
</ul>
</nav>
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="card mb-4 col-md-12" id="card_main_load" style="display:none;">
<div class="card-body">
<div class="row">
<div id='graph-container'></div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/plugin/vis/vis.min.js"></script>
<script src="/static/assets/js/plugin/vis/vis-network.min.js"></script>
<script src="/static/assets/js/plugin/bootstrap-slider/bootstrap-slider.min.js"></script>
<script src="/static/assets/js/plugin/select/select2.js"></script>
<script src="/static/assets/js/plugin/select/bootstrap-select.min.js"></script>
<script src="/static/assets/js/iris/case.js"></script>
<script src="/static/assets/js/iris/case.graph.js"></script>
{% endblock javascripts %}

View File

@@ -0,0 +1,86 @@
{% extends "layouts/default_ext.html" %}
{% block title %} Case Graph Timeline {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-datetime.css">
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/select2.css">
<link rel="stylesheet" href="/static/assets/css/vis.graph.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
<nav class="navbar navbar-header navbar-expand-lg bg-primary-gradient">
<ul class="container-fluid mt-3 mb--2">
<ul class="navbar-nav">
<li class="nav-item hidden-caret">
<a class="menu-title btn btn-dark btn-sm" href="visualize?cid={{session['current_case'].case_id}}">No group</a>
</li>
<li class="nav-item hidden-caret">
<a class="menu-title btn btn-dark btn-sm" href="visualize?cid={{session['current_case'].case_id}}&group-by=asset"><span class="text-decoration-none">Group by asset</span></a>
</li>
<li class="nav-item hidden-caret">
<a class="menu-title btn btn-dark btn-sm" href="visualize?cid={{session['current_case'].case_id}}&group-by=category">Group by category</a>
</li>
</ul>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item hidden-caret">
<button class="btn btn-primary btn-sm" onclick="refresh_timeline_graph();">
<span class="menu-title">Refresh</span>
</button>
</li>
</ul>
</ul>
</nav>
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="page-inner">
<div class="row">
<div class="col-12">
<div class="card" id="card_main_load" style="display:none;">
<div class="card-body">
<div id='visualization'></div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/core/moments.min.js"></script>
<script src="/static/assets/js/core/bootstrap-datetimepicker.min.js"></script>
<script src="/static/assets/js/plugin/tagsinput/suggesttag.js"></script>
<script src="/static/assets/js/plugin/select/select2.js"></script>
<script src="/static/assets/js/plugin/select/bootstrap-select.min.js"></script>
<script src="/static/assets/js/iris/case.js"></script>
<script src="/static/assets/js/iris/case.timeline.visu.js"></script>
<script src="/static/assets/js/plugin/vis/vis.graph.js"></script>
<script>
$(document).ready(function(){
show_loader();
refresh_timeline_graph();
});
</script>
{% endblock javascripts %}

View File

@@ -0,0 +1,149 @@
{% extends "layouts/default_ext.html" %}
{% block title %} Case IOC {% endblock title %}
{% block stylesheets %}
{% include 'includes/header_case.html' %}
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<nav class="navbar navbar-header navbar-expand-lg pt-2 pb-2 bg-primary-gradient">
<div class="container-fluid">
<div class="collapse" id="search-nav">
<div id="tables_button"></div>
</div>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item">
<button class="btn btn-primary btn-sm" onclick="reload_iocs();">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item">
<button class="btn btn-dark btn-sm" onclick="add_ioc();">
<span class="menu-title">Add IOC</span>
</button>
</li>
<li class="nav-item">
<div class="dropdown">
<button class="btn btn-sm btn-border btn-black" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="menu-title"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick="fire_upload_iocs();">Upload CSV of IOCs</a>
</div>
</div>
</li>
</ul>
</div>
</nav>
<div class="page-inner">
<div class="row">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="col-md-12">
<div class="card" id="card_main_load" style="display:none;">
<div class="card-body">
<table class="table display wrap col-border table-striped table-hover" width="100%" cellspacing="0" id="ioc_table" >
<thead>
<tr>
<th>Value</th>
<th>Type</th>
<th>Description</th>
<th>Tags</th>
<th>Linked cases</th>
<th>TLP</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Value</th>
<th>Type</th>
<th>Description</th>
<th>Tags</th>
<th>Linked cases</th>
<th>TLP</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% include 'includes/footer.html' %}
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_add_ioc" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_add_ioc_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_upload_ioc" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<form method="post" action="" id="form_upload_ioc">
<div class="modal-content">
<div class="modal-header">
<h5>Upload IOC list (CSV format)</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="ioc_format" class="placeholder">Expected CSV File format</label>
<textarea class="form-control col-md-12 col-sm-12 sizable-textarea" rows="2" disabled>ioc_value,ioc_type,ioc_description,ioc_tags,ioc_tlp
&lt;Value&gt;,&lt;Type&gt;,&lt;Description&gt;,&lt;TLP&gt;,Tags separated with &quot;|&quot;</textarea>
</div>
<div class="form-group">
<label class="placeholder">CSV File format example</label>
<textarea class="form-control col-md-12 col-sm-12 sizable-textarea" rows="3" disabled>ioc_value,ioc_type,ioc_description,ioc_tags,ioc_tlp
1.1.1.1,IP,Cloudfare DNS IP address,Cloudfare|DNS,green
wannacry.exe,File,Wannacry sample found,Wannacry|Malware|PE,amber</textarea>
</div>
<div class="form-group">
<label class="placeholder">Choose CSV file to import : </label>
<input id="input_upload_ioc" type="file" accept="text/csv">
</div>
</div>
<div class='invalid-feedback' id='ioc-invalid-msg'></div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark mr-auto" onclick="generate_sample_csv();">Download sample CSV</button>
<button type="button" class="btn btn-outline-success" onclick="upload_ioc();">Upload</button>
</div>
</div><!-- /.modal-content -->
</form>
</div><!-- /.modal-dialog -->
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/iris/case.ioc.js"></script>
{% endblock javascripts %}

View File

@@ -0,0 +1,59 @@
<div class="modal shortcut_modal bg-shadow-gradient" id="shortcutModal" tabindex="-1" aria-labelledby="shortcutModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="shortcutModalLabel">Shortcuts</h5>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
<div class="modal-body">
<table class="table">
<thead>
<tr>
<th scope="col">Shortcut</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>CTRL-S</td>
<td>Save note</td>
</tr>
<tr>
<td>CTRL-B</td>
<td>Bold</td>
</tr>
<tr>
<td>CTRL-I</td>
<td>Italic</td>
</tr>
<tr>
<td>CTRL-SHIFT-1</td>
<td>Heading 1</td>
</tr>
<tr>
<td>CTRL-SHIFT-2</td>
<td>Heading 2</td>
</tr>
<tr>
<td>CTRL-SHIFT-3</td>
<td>Heading 3</td>
</tr>
<tr>
<td>CTRL-SHIFT-4</td>
<td>Heading 4</td>
</tr>
<tr>
<td>CTRL-`</td>
<td>Insert code</td>
</tr>
<tr>
<td>CTRL-K</td>
<td>Insert link</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,134 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case notes {% endblock title %} {% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<nav class="navbar navbar-header navbar-expand-lg pt-2 pb-2 bg-primary-gradient">
<div class="container-fluid">
<div class="collapse search-flex" id="search-nav">
<ul class="list-group list-group-bordered hidden-caret" id="notes_search_list"></ul>
<input type="text" class="form-control mr-3" style="max-width:400px;" id="search_note_input" onkeyup="search_notes()" placeholder="Search in notes..">
</div>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item">
<button class="btn btn-primary btn-sm" onclick="draw_kanban();">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item">
<button class="btn btn-dark btn-sm" onclick="add_remote_groupnote();">
<span class="menu-title">Add notes group</span>
</button>
</li>
</ul>
</div>
</nav>
<div class="page-inner">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div id="empty-set-notes" style="display:none;">
<h4 class="text-dark text-sm text-center ml-mr-auto">It looks pretty empty <i class="fa-solid fa-mug-hot ml-2"></i></h4>
<h4 class="text-dark text-sm text-center ml-mr-auto"><a href="#" onclick="add_remote_groupnote();">Click here to add the first note group</a></h4>
</div>
<div class="row" id="card_main_load" style="display:none;">
<div class="container-fluid">
<div class="float-right mt-2 col">
</div>
</div>
</div>
<div class="row">
<div id="myKanban" class="board">
</div>
</div>
<div id="side_timeline">
<button class="btn btn-round btn-primary-success btn_over_page_i" onclick="add_remote_groupnote();"><i class="fas fa-plus-circle"></i></button>
</div>
</div>
<div class="kanban-item row" id="_subnote_" style="display: none;" onclick="edit_note(this);" title="">
<a href="#" class="kanban-title text-truncate w-100" draggable="false">New note</a><br />
<em><small href="#" class="text-sm text-muted kanban-info" draggable="false"><i
class="flaticon-tool mr-1"></i>Hello</small></em>
<iris_note style="display: none;" id="xqx00qxq">New note</iris_note>
<div class="kanban-badge avatar-group-note col-12" id="kanban_badge_">
</div>
</div>
<div data-id="_todo" class="kanban-board" id="group_" title="" draggable="false" style="display: none;">
<header class="kanban-board-header">
<div class="row">
<div class="col-8">
<div contenteditable="true" maxlength="25" class="kanban-title-board" onclick="">Note group</div>
</div>
<div class="col">
<div class="kanban-title-button">
<div class="row mr-1">
<button class="mr-2" onclick="" style="display: none;"><i
class="fas fa-check-circle text-success"></i></button>
<button class="mr-2" onclick=""><i class="fas fa-plus-circle "></i></button>
<div class="dropdown dropdown-kanban ">
<button class="dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<i class="icon-options-vertical"></i>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton ">
<a class="dropdown-item" href="#" draggable="false">Delete</a>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
<main class="kanban-drag" id="_main">
</main>
</div>
<div class="modal modal-case-focus" tabindex="-1" role="dialog" id="modal_note_detail" data-backdrop="true">
<div class="modal-dialog modal-xxl modal-xl" role="document">
<div class="modal-content" id="info_note_modal_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% endif %}
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/plugin/sortable/sortable.js "></script>
<script src="/static/assets/js/core/socket.io.js"></script>
<script src="/static/assets/js/iris/case.notes.js "></script>
<script>
/* Wait for document to be ready before loading the kanban board */
$(document).ready(function () {
/* load board */
boardNotes.init();
setInterval(function() { check_update('notes/state'); }, 3000);
draw_kanban();
});
</script>
{% endblock javascripts %}

View File

@@ -0,0 +1,105 @@
{% extends "layouts/default_ext.html" %} {% block title %} Case Evidences {% endblock title %} {% block stylesheets %}
{% include 'includes/header_case.html' %}
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<nav class="navbar navbar-header navbar-expand-lg pt-2 pb-2 bg-primary-gradient">
<div class="container-fluid">
<div class="collapse" id="search-nav">
<div id="tables_button"></div>
</div>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item">
<button class="btn btn-primary btn-sm" onclick="reload_rfiles(true);">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item">
<button onclick="add_modal_rfile()" class="btn btn-dark btn-sm">
<span class="menu-title">Register Evidence</span>
</button>
</li>
</ul>
</div>
</nav>
<div class="page-inner">
<div class="row">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="col-md-12">
<div class="card" id="card_main_load" style="display:none;">
<div class="card-body">
<div class="table-responsive" id="rfiles_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display wrap col-border table-striped table-hover dataTable" width="100%"
cellspacing="0" id="rfiles_table">
<thead>
<tr>
<th>Name</th>
<th>Date</th>
<th>Hash</th>
<th>Size (bytes)</th>
<th>Description</th>
<th>Added by</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Date</th>
<th>Hash</th>
<th>Size(bytes)</th>
<th>Description</th>
<th>Added by</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_add_rfiles" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_add_rfiles_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endif %}
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/iris/case.rfiles.js"></script>
<script>
</script>
{% endblock javascripts %}

View File

@@ -0,0 +1,109 @@
{% extends "layouts/default_ext.html" %}
{% block title %} Case Tasks {% endblock title %}
{% block stylesheets %}
{% include 'includes/header_case.html' %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-multiselect.min.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<nav class="navbar navbar-header navbar-expand-lg pt-2 pb-2 bg-primary-gradient">
<div class="container-fluid">
<div class="collapse" id="search-nav">
<div id="tables_button"></div>
</div>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item">
<button class="btn btn-primary btn-sm" onclick="get_tasks();">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item">
<button class="btn btn-dark btn-sm" onclick="add_task();">
<span class="menu-title">Add task</span>
</button>
</li>
</ul>
</div>
</nav>
<div class="page-inner ">
<div class="row ">
<div class="col-md-12">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<section class="card" id="card_main_load" style="display:none;">
<div class="card-body">
<div class="table-responsive" id="tasks_table_wrapper">
<table class="table display wrap col-border table-striped table-hover dataTable" width="100%"
cellspacing="0" id="tasks_table">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Assigned to</th>
<th>Open date</th>
<th>Tags</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Assigned to</th>
<th>Open date</th>
<th>Tags</th>
</tr>
</tfoot>
</table>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_add_task" data-backdrop="true">
<div class="modal-xxl modal-dialog" role="document">
<div class="modal-content" id="modal_add_task_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endif %}
</div>
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/plugin/datatables/dataTables.cellEdit.js"></script>
<script src="/static/assets/js/plugin/select/bootstrap-multiselect.min.js"></script>
<script src="/static/assets/js/iris/case.tasks.js"></script>
{% endblock javascripts %}

View File

@@ -0,0 +1,178 @@
{% extends "layouts/default_ext.html" %}
{% block title %} Case Timeline {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/bootstrap-datetime.css">
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/select2.css">
{% endblock stylesheets %}
{% block content %}
{% include 'includes/navigation_ext.html' %}
{% include 'includes/sidenav.html' %}
<div class="main-panel">
<div class="content">
<!-- Navbar Header -->
<nav class="navbar navbar-header navbar-expand-lg bg-primary-gradient">
{{ form.hidden_tag() }}
<ul class="container-fluid mt-3 mb--2">
<ul class="navbar-nav col-8">
<li class="nav-item hidden-caret col-12">
<div class="row">
<div id='timeline_filtering' class="col-9 pt-2 pl-2" style="border-radius:3px;" ></div>
<button class="btn btn-sm btn-light ml-2 pt-2" onclick="filter_timeline();">
Apply filter
</button>
<button class="btn btn-sm btn-light ml-1 pt-2" onclick="reset_filters();">
Reset
</button>
<i class="ml-1 mt-1 fa-regular text-white fa-circle-question" title="Filter help" style="cursor:pointer;" onclick="show_timeline_filter_help();"></i>
</div>
</li>
</ul>
<ul class="navbar-nav topbar-nav ml-md-auto align-items-center page-navigation page-navigation-style-2 page-navigation-secondary">
<li class="nav-item ml-2">
<span class="text-white text-sm mr-2" id="last_resfresh">Loading</span>
</li>
<li class="nav-item hidden-caret">
<button class="btn btn-primary btn-sm" onclick="get_or_filter_tm();">
<span class="menu-title">Refresh</span>
</button>
</li>
<li class="nav-item hidden-caret">
<button class="btn btn-dark btn-sm" onclick="add_event();">
<span class="menu-title">Add event</span>
</button>
</li>
<li class="nav-item">
<div class="dropdown">
<button class="btn btn-sm btn-border btn-black" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="menu-title"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="timeline/visualize?cid={{session['current_case'].case_id}}"> Visualize</a>
<a class="dropdown-item" href="timeline/visualize?cid={{session['current_case'].case_id}}&group-by=asset"> Visualize by asset</a>
<a class="dropdown-item" href="timeline/visualize?cid={{session['current_case'].case_id}}&group-by=category">Visualize by category</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" onclick="timelineToCsv();"><small class="fa fa-download mr-2"></small> Download as CSV</a>
<a class="dropdown-item" href="#" onclick="timelineToCsvWithUI();"><small class="fa fa-download mr-2"></small> Download as CSV with user info</a>
<!-- BEGIN_RS_CODE -->
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" onclick="fire_upload_csv_events();"><small class="fa fa-upload mr-2"></small> Upload CSV of events</a>
<!-- END_RS_CODE -->
</div>
</div>
</li>
</ul>
</ul>
</nav>
{% if current_user.is_authenticated %}
<div class="page-inner">
<div class="row">
<div class="loader1 text-center ml-mr-auto" id="loading_msg">Loading...</div>
<div class="col-md-12" id="card_main_load" style="display:none;">
<div id="paginator"></div>
<ul class="timeline" id="timeline_list">
</ul>
</div>
</div>
<div id="side_timeline">
<div class="btn_over_page_a">
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-white" title="color1" onclick="events_set_attribute('event_color', '#fff')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-primary" title="color2" onclick="events_set_attribute('event_color', '#1572E899')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-secondary" title="color3" onclick="events_set_attribute('event_color', '#6861CE99')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-info" title="color4" onclick="events_set_attribute('event_color', '#48ABF799')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-success" title="color5" onclick="events_set_attribute('event_color', '#31CE3699')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-danger" title="color5" onclick="events_set_attribute('event_color', '#F2596199')"></button>
<button class="btn btn-round btn-light btn-conditional-2 colorinput-color bg-warning" title="color5" onclick="events_set_attribute('event_color', '#FFAD4699')"></button>
<button class="btn btn-round btn-light btn-conditional" title="Change color" onclick="toggle_colors()"><i class="fas fa-tint"></i></button>
</div>
<button class="btn btn-round btn-light btn_over_page_delete btn-conditional" title="Delete selected events" onclick="events_bulk_delete();"><i class="fas fa-trash text-danger"></i></button>
<button class="btn btn-round btn-light btn_over_page_b btn-conditional" title="Toggle Summary" onclick="events_set_attribute('event_in_summary')"><i class="fas fa-newspaper"></i></button>
<button class="btn btn-round btn-light btn_over_page_c btn-conditional" title="Toggle Graph" onclick="events_set_attribute('event_in_graph')"><i class="fas fa-share-alt"></i></button>
<button class="btn btn-round btn-light btn_over_page_d" title="Select rows" onclick="toggle_selector();" id="selector-btn"><i class="fas fa-check"></i></button>
<button class="btn btn-round btn-light btn_over_page_e" title="Add new event" onclick="add_event();"><i class="fas fa-plus-circle"></i></button>
<button class="btn btn-round btn-light btn_over_page_f" title="Refresh" onclick="get_or_filter_tm();"><i class="fas fa-redo-alt"></i></button>
<button class="btn btn-round btn-light btn_over_page_g" title="Go at the top" onclick="to_page_up();"><i class="fas fa-arrow-up"></i></button>
<button class="btn btn-round btn-light btn_over_page_h" title="Go at the bottom" onclick="to_page_down();"><i class="fas fa-arrow-down"></i></button>
<button class="btn btn-round btn-light btn_over_page_i" title="Toggle compact view" onclick="toggle_compact_view();"><i class="fas fa-list"></i></button>
</div>
</div>
<div class="modal shadow-lg" tabindex="-1" id="modal_add_event" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_add_event_content">
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endif %}
</div>
<!-- BEGIN RS_CODE -->
<div class="modal " tabindex="-1" role="dialog" id="modal_upload_csv_events" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<form method="post" action="" id="form_upload_csv_events">
<div class="modal-content">
<div class="modal-header">
<h5>Upload events list (CSV format)</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="csv_format" class="placeholder">Expected Events CSV File format</label>
<textarea id="csv_format" class="form-control col-md-12 col-sm-12 sizable-textarea" rows="2" disabled>event_timestamp,event_title,event_description,linked_assets,linked_IoCs,event_tags (separated with &quot;|&quot;),event_color,event_raw_data</textarea>
</div>
<div class="form-group">
<label class="placeholder">Events CSV File format example</label>
<textarea class="form-control col-md-12 col-sm-12 sizable-textarea" rows="3" disabled>
event_date,event_tz,event_title,event_category,event_content,event_raw,event_source,event_assets,event_iocs,event_tags
"2023-03-26T03:00:30.000","+00:00","An event","Unspecified","Event description","raw","source","","","defender|malicious"
"2023-03-23T12:00:35.000","+00:00","An event","Legitimate","Event description","raw","source","","","admin_action"
</textarea>
</div>
<div class="form-group">
<label class="placeholder">Choose CSV file to import : </label>
<input id="input_upload_csv_events" type="file" accept="text/csv">
</div>
</div>
<div class='invalid-feedback' id='ioc-invalid-msg'></div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark mr-auto" onclick="generate_events_sample_csv();">Download sample CSV</button>
<button type="button" class="btn btn-outline-success" onclick="upload_csv_events();">Upload</button>
</div>
</div><!-- /.modal-content -->
</form>
</div><!-- /.modal-dialog -->
</div>
<!-- END_RS_CODE -->
{% include 'includes/footer.html' %}
</div>
{% endblock content %}
{% block javascripts %}
{% include 'includes/footer_case.html' %}
<script src="/static/assets/js/iris/case.timeline.js"></script>
<script src="/static/assets/js/timeline.js"></script>
{% endblock javascripts %}

View File

@@ -0,0 +1,201 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{{ "Asset #{}".format(asset.asset_id) if asset.asset_name else "Add asset" }}</h4>
<small><a class="text-muted">{{ "#{}".format(asset.asset_uuid) if asset.asset_uuid else "" }}</a></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
{% if asset.asset_id %}
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="asset_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{asset.asset_id}});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("asset", {{asset.asset_id}});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
</div>
</div>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ asset.asset_id }}, 'assets')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
{% endif %}
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_asset', '{{ "Asset {}".format(asset.asset_name) if asset.asset_name else "Add asset" }}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_new_asset">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-row ml-2">
<div class="form-group col-6">
<label for="name" class="placeholder">Asset Name *</label>
{{ form.asset_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group col-6">
<label for="asset_type" class="placeholder">Asset Type *</label>
{{ form.asset_type_id(class="selectpicker form-control") }}
</div>
</div>
<div class="form-group mt-3">
<label for="asset_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_asset_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2" onclick="preview_asset_description();" id="asset_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="asset_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_asset_desc_content">
<div id="asset_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if asset %}{{ asset.asset_description }}{% endif %}</div>
<textarea id="asset_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_asset_description" style="display:none">
<div id="target_asset_desc"></div>
</div>
</div>
</div>
</div>
<div class="form-row ml-2">
<div class="form-group col-6">
<label for="asset_domain" class="placeholder">Domain</label>
{{ form.asset_domain(class='form-control', autocomplete="off") }}
</div>
<div class="form-group col-6">
<label for="asset_ip" class="placeholder">IP</label>
{{ form.asset_ip(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group">
<a class="btn btn-light btn-sm" data-toggle="collapse" href="#collapseAddInfo" role="button" aria-expanded="false" aria-controls="collapseAddInfo">> Additional information</a>
<div class="collapse" id="collapseAddInfo">
<div class="card card-body">
<label for="asset_info" class="placeholder">Additional information</label>
{{ form.asset_info(class='form-control col-md-12 col-sm-12 sizable-textarea', autocomplete="off") }}
</div>
</div>
</div>
<div class="form-row ml-2">
<div class="form-group col-6">
<label for="asset_compromise_status_id" class="placeholder mt-2">Compromise Status </label>
{{ form.asset_compromise_status_id(class="selectpicker col-9") }}
</div>
<div class="form-group col-6">
<label for="analysis_status_id" class="placeholder mt-2">Analysis Status </label>
{{ form.analysis_status_id(class="selectpicker col-9 float-right") }}
</div>
</div>
<div class="form-group">
<label for="asset_tags">Asset tags
</label>
<input type="text" id="asset_tags"
class="form-control col-md-12" {% if asset.asset_tags %} value="{{ asset.asset_tags }}" {% endif %}/>
</div>
<div class="form-group" data-select2-id="7">
<label>Related IOC</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="ioc_links" name="ioc_links" class="form-control select2-hidden-accessible ml-12" multiple="" data-select2-id="ioc_links" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
{% if asset.asset_id %}
<button type="button" class="btn btn-outline-danger ml-4 mt-5"
onclick="delete_asset({{ asset.asset_id }});">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_asset">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_asset">Save</button>
{% endif %}
</div>
</div>
<script>
$('form#form_new_case').validate();
$('#asset_tags').amsifySuggestags({
printValues: false,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ]
});
$('#asset_type_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white",
});
$('#analysis_status_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white"
});
$('#analysis_status_id').selectpicker('val', '1');
$('#asset_compromise_status_id').selectpicker({
liveSearch: true,
title: "To be determined",
style: "btn-outline-white"
});
$('#asset_compromise_status_id').selectpicker('val', '0');
</script>
{% if asset.asset_id %}
<script>
$('#asset_type_id').selectpicker('val', '{{ asset.asset_type_id }}');
</script>
{% endif %}
{% if asset.analysis_status_id %}
<script>
$('#analysis_status_id').selectpicker('val', '{{ asset.analysis_status_id }}');
</script>
{% endif %}
{% if asset.asset_compromise_status_id %}
<script>
$('#asset_compromise_status_id').selectpicker('val', '{{ asset.asset_compromise_status_id }}');
</script>
{% endif %}
{% if ioc %}
<script>
var data = [
{% for e in ioc %}
{
id: {{ e.ioc_id }},
text: {{ e.ioc_value| tojson }}
},
{% endfor %}
];
$('#ioc_links').select2({ data: data });
</script>
{% endif %}
{% if ioc_prefill %}
<script>
$('#ioc_links').val([
{% for ioc in ioc_prefill %} {{ ioc[0] }}, {% endfor %}
]);
$('#ioc_links').trigger('change');
</script>
{% endif %}

View File

@@ -0,0 +1,327 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{% if event.event_id %} Event ID #{{ event.event_id }} {% else %} Add event {% endif %}
{% if event.modification_history %}
<i class="fa-solid fa-clock-rotate-left ml-3 mt-2" data-toggle="popover" data-html="true" id="pop_history" style="cursor: pointer;"
title="Modifications history"
data-content="<small>{% for mod in event.modification_history %}<code>{{ mod|format_datetime('%Y-%m-%d %H:%M') }}</code> - {{ event.modification_history[mod].action }} by {{ event.modification_history[mod].user }}<br/>{% endfor %}</small>">
</i>
{% endif %}
</h4>
<small><i class="text-muted">{% if event.event_uuid %}#{{ event.event_uuid }}{% endif %}</i></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col ">
<div class="row float-right">
{% if event.event_id %}
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="event_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{event.event_id}});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("event", {{event.event_id}});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
<a class="dropdown-item" href="#" onclick='duplicate_event({{event.event_id}});return false;'><i class="fa fa-clone mr-2"></i>Duplicate</a>
</div>
</div>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ event.event_id }}, 'timeline/events')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
{% endif %}
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_event', '{% if event.event_id %} Event ID #{{ event.event_id }} {% else %} Add event {% endif %}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="float-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_new_event">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="row">
<div class="form-group col-xl-5 col-md-12">
<label for="event_title" class="placeholder">{{ form.event_title.label.text }} *</label>
{{ form.event_title(class='form-control col-md-12 col-sm-12', autocomplete="off") }}
</div>
<div class="form-group col-xl-7 col-md-12">
<label for="event_timetitle" class="placeholder ml-2">Event Time *</label>
<div class="row ml-2" id="event_date_inputs">
<input class="form-control col-5 mr-2" type="date" id="event_date" {% if event.event_date_wtz %} value="{{ event.event_date_wtz.strftime('%Y-%m-%d') }}"{% endif %}>
<span></span>
<input class="form-control col-4" type="time" step="0.001" id="event_time" {% if event.event_date_wtz %} value="{{ event.event_date_wtz.strftime('%H:%M:%S.%f')[:-3] }}" {% else %} value="00:00:00.000" {% endif %}>
<span></span>
<input class="form-control col-2" type="text" id="event_tz" {% if event.event_tz %} value="{{ event.event_tz }}" {% else %} value="+00:00" {% endif %}>
<button class="btn btn-sm btn-outline-white" type="button" onclick="show_time_converter();return false;"><i class="fas fa-magic"></i></button>
</div>
<div class="row ml-2" id="event_date_convert" style="display:none;">
<div class="input-group ">
<input class="form-control col-9" type="text" id="event_date_convert_input" placeholder="Enter date in any format and submit to try auto-parsing">
<div class="input-group-append">
<button class="btn btn-sm btn-outline-secondary mr-2" type="button" onclick="time_converter();return false;">Submit</button>
<button class="btn btn-sm btn-outline" type="button" onclick="hide_time_converter();return false;"><i class="fas fa-magic"></i></button>
</div>
</div>
<span id="convert_bad_feedback" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="form-group mt-3 col-12">
<label for="event_content" class="placeholder">Event description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_event_desc();" >
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_event_description();" id="event_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="event_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_event_desc_content">
<div id="event_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if event.event_content %}{{ event.event_content }}{% endif %}</div>
<textarea id="event_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_event_description" style="display:none">
<div id="target_event_desc"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-12 mt-1">
<a class="btn btn-light btn-sm" data-toggle="collapse" href="#collapseRawEvent" role="button" aria-expanded="false" aria-controls="collapseRawEvent">> Edit raw event data</a>
<div class="collapse" id="collapseRawEvent">
<div class="card card-body">
<label for="event_raw" class="placeholder">{{ form.event_raw.label.text }}</label>
{{ form.event_raw(class='form-control sizable-textarea', autocomplete="off") }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-xl-6 col-md-12">
<label for="event_title" class="placeholder">{{ form.event_source.label.text }}</label>
{{ form.event_source(class='form-control col-md-12 col-sm-12', autocomplete="off") }}
</div>
<div class="form-group col-xl-6 col-md-12">
<label for="event_tags">Event tags
</label>
<input type="text" id="event_tags"
class="form-control col-md-12" {% if event.event_tags %} value="{{ event.event_tags }}" {% endif %}/>
</div>
</div>
<div class="row">
<div class="form-group col-12">
<label for="event_assets">Link to assets
</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="event_assets" name="event_assets" class="form-control select2-hidden-accessible ml-12" multiple="" data-select2-id="event_assets" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-10">
<label for="event_assets">Link to IOCs
</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="event_iocs" name="event_iocs" class="form-control select2-hidden-accessible ml-12" multiple="" data-select2-id="event_iocs" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
<div class="form-group col-2">
<div class="form-check">
<label class="form-check-label mt-3">
<input class="form-check-input" type="checkbox" id="event_sync_iocs_assets" checked>
<span class="form-check-sign"> Push IOCs to assets
<i class="ml-1 mt-1 fa-regular fa-circle-question" title="If checked, the IOCs related to this event will be associated with the specified assets" style="cursor:pointer;"></i>
</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-5">
<label for="event_category_id" class="form-label">{{ form.event_category_id.label.text }}</label>
<div class="row col-12">
{{ form.event_category_id(class="selectpicker") }}
</div>
</div>
<div class="form-group col-xl-2 col-md-12">
<div class="form-check">
<label class="form-check-label mt-3">
{{ form.event_in_summary(class="form-check-input", type="checkbox") }}
<span class="form-check-sign"> Add to summary
<i class="ml-1 mt-1 fa-regular fa-circle-question" title="If checked, the event will be integrated in the Timeline Visualization" style="cursor:pointer;"></i>
</span>
</label>
</div>
</div>
<div class="form-group col-xl-2 col-md-12">
<div class="form-check">
<label class="form-check-label mt-3">
{{ form.event_in_graph(class="form-check-input", type="checkbox") }}
<span class="form-check-sign"> Display in graph
<i class="ml-1 mt-1 fa-regular fa-circle-question" title="If checked, the event will be integrated in the Graph section of the case" style="cursor:pointer;"></i>
</span>
</label>
</div>
</div>
<div class="form-group col-xl-3 col-md-12">
<label class="form-label">Event color</label>
<div class="row gutters-xs">
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#fff" {% if event.event_color == "#fff" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-white"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#1572E899" {% if event.event_color == "#1572E899" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-primary"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#6861CE99" {% if event.event_color == "#6861CE99" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-secondary"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#48ABF799" {% if event.event_color == "#48ABF799" %} checked="checked" {% endif %}class="colorinput-input">
<span class="colorinput-color bg-info"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#31CE3699" {% if event.event_color == "#31CE3699" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-success"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#F2596199" {% if event.event_color == "#F2596199" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-danger"></span>
</label>
</div>
<div class="col-auto">
<label class="selectgroup-item">
<input name="event_color" type="radio" value="#FFAD4699" {% if event.event_color == "#FFAD4699" %} checked="checked" {% endif %} class="colorinput-input">
<span class="colorinput-color bg-warning"></span>
</label>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
{% if event.event_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_event({{ event.event_id }} );">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_event"
onclick="update_event({{ event.event_id }} );">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_event">Save</button>
{% endif %}
</div>
</div>
<script>
$('#event_tags').amsifySuggestags({
printValues: true,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ],
whiteList: false,
selectOnHover: false,
});
$('[data-toggle="popover"]').popover();
$('#event_category_id').selectpicker({
width:"100%",
liveSearch: true,
title: "None",
style: "btn-outline-white",
});
</script>
{% if assets %}
<script>
var data = [
{% for e in assets %}
{
id: {{ e.asset_id }},
text: {{ e.asset_name| tojson }}
},
{% endfor %}
];
$('#event_assets').select2({ data: data });
</script>
{% endif %}
{% if iocs %}
<script>
var data = [
{% for e in iocs %}
{
id: {{ e.ioc_id }},
text: {{ e.ioc_value| tojson }}
},
{% endfor %}
];
$('#event_iocs').select2({ data: data });
</script>
{% endif %}
{% if category %}
<script>
$('#event_category_id').val([
{{ category[0].id }},
]);
$('#event_category_id').trigger('change');
</script>
{% else %}
<script>
$('#event_category_id').val(1);
$('#event_category_id').trigger('change');
</script>
{% endif %}
{% if assets_prefill %}
<script>
$('#event_assets').val([
{% for asset in assets_prefill %} {{ asset }}, {% endfor %}
]);
$('#event_assets').trigger('change');
</script>
{% endif %}
{% if iocs_prefill %}
<script>
$('#event_iocs').val([
{% for ioc in iocs_prefill %} {{ ioc }}, {% endfor %}
]);
$('#event_iocs').trigger('change');
</script>
{% endif %}

View File

@@ -0,0 +1,150 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{% if ioc.ioc_id %}Edit IOC #{{ ioc.ioc_id }}{% else %} Add IOC {% endif %}</h4>
<small><i class="text-muted">{% if ioc.ioc_uuid %}#{{ ioc.ioc_uuid }}{% endif %}</i></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
{% if ioc.ioc_id %}
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="ioc_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{ioc.ioc_id}});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("ioc", {{ioc.ioc_id}});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
</div>
<div class="dropdown-menu pull-right" aria-labelledby="dropdownMenuButton">
</div>
</div>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ ioc.ioc_id }}, 'ioc')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
{% endif %}
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_ioc', '{% if ioc.ioc_id %}Edit IOC #{{ ioc.ioc_id }} {% else %} Add IOC {% endif %}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="float-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_new_ioc">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group row">
<div class="col-6">
<label for="ioc_type" class="mr-4">Type *
</label>
{{ form.ioc_type_id(class="selectpicker pl--6 col-10") }}
</div>
<div class="col-6">
<label for="ioc_type" class="mr-4">TLP *
</label>
{{ form.ioc_tlp_id(class="selectpicker pl--6 col-10") }}
</div>
</div>
<div class="form-group">
<label for="ioc_value" class="placeholder">{{ form.ioc_value.label.text }} *</label>
{{ form.ioc_value(class='form-control col-md-12 col-sm-12 sizable-textarea', autocomplete="off") }}
</div>
{% if not ioc.ioc_id %}
<div class="form-group col-2">
<div class="form-check">
<label class="form-check-label mt-3">
<input class="form-check-input" type="checkbox" id="ioc_one_per_line" checked>
<span class="form-check-sign"> One IOC per line
<i class="ml-1 mt-1 fa-regular fa-circle-question" title="If checked, each new line will create a new IOC" style="cursor:pointer;"></i>
</span>
</label>
</div>
</div>
{% endif %}
<div class="form-group mt-3">
<label for="ioc_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_ioc_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_ioc_description();" id="ioc_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="ioc_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_ioc_desc_content">
<div id="ioc_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if ioc and ioc.ioc_description %}{{ ioc.ioc_description }}{% endif %}</div>
<textarea id="ioc_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_ioc_description" style="display:none">
<div id="target_ioc_desc"></div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="ioc_tags">IOC tags
</label>
<input type="text" id="ioc_tags"
class="form-control col-md-12" {% if ioc.ioc_tags %} value="{{ ioc.ioc_tags }}" {% endif %}/>
</div>
<div class='invalid-feedback' id='ioc-invalid-msg'></div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
{% if ioc.ioc_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_ioc('{{ ioc.ioc_id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_ioc"
onclick="update_ioc('{{ ioc.ioc_id }}');">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_ioc">Save</button>
{% endif %}
</div>
<script>
$('form#form_new_ioc').validate();
$('#ioc_tags').amsifySuggestags({
printValues: false,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ]
});
$('#ioc_type_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white",
size: 10
});
$('#ioc_tlp_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white",
});
{% if ioc.ioc_id %}
$('#ioc_type_id').selectpicker('val', '{{ioc.ioc_type_id}}');
$('#ioc_tlp_id').selectpicker('val', '{{ioc.ioc_tlp_id}}');
{% else %}
$('#ioc_tlp_id').selectpicker('val', '2');
{% endif %}
</script>

View File

@@ -0,0 +1,162 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">Add multiple assets</h4>
<small><a class="text-muted"></a></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_asset', 'Add asset');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_new_assets">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-row ml-2">
<div class="form-group col-12">
<label for="asset_type" class="placeholder">Assets Type *</label>
{{ form.asset_type_id(class="selectpicker form-control") }}
</div>
<div class="form-group col-12">
<label for="name" class="placeholder">Assets Name *</label>
<textarea class="form-control sizable-textarea" autocomplete="off" rows="1" name="assets_name" id="assets_name" placeholder="One asset per line"></textarea>
</div>
</div>
<div class="form-group mt-3">
<label for="asset_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_asset_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2" onclick="preview_asset_description();" id="asset_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="asset_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_asset_desc_content">
<div id="asset_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if asset %}{{ asset.asset_description }}{% endif %}</div>
<textarea id="asset_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_asset_description" style="display:none">
<div id="target_asset_desc"></div>
</div>
</div>
</div>
</div>
<div class="form-row ml-2">
<div class="form-group col-6">
<label for="asset_domain" class="placeholder">Domain</label>
{{ form.asset_domain(class='form-control', autocomplete="off") }}
</div>
<div class="form-group col-6">
<label for="asset_ip" class="placeholder">IP</label>
{{ form.asset_ip(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group">
<a class="btn btn-light btn-sm" data-toggle="collapse" href="#collapseAddInfo" role="button" aria-expanded="false" aria-controls="collapseAddInfo">> Additional information</a>
<div class="collapse" id="collapseAddInfo">
<div class="card card-body">
<label for="asset_info" class="placeholder">Additional information</label>
{{ form.asset_info(class='form-control col-md-12 col-sm-12 sizable-textarea', autocomplete="off") }}
</div>
</div>
</div>
<div class="form-row ml-2">
<div class="form-group col-6">
<label for="asset_compromise_status_id" class="placeholder mt-2">Compromise Status </label>
{{ form.asset_compromise_status_id(class="selectpicker col-9") }}
</div>
<div class="form-group col-6">
<label for="analysis_status_id" class="placeholder mt-2">Analysis Status </label>
{{ form.analysis_status_id(class="selectpicker col-9 float-right") }}
</div>
</div>
<div class="form-group">
<label for="asset_tags">Asset tags
</label>
<input type="text" id="asset_tags"
class="form-control col-md-12"/>
</div>
<div class="form-group" data-select2-id="7">
<label>Related IOC</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="ioc_links" name="ioc_links" class="form-control select2-hidden-accessible ml-12" multiple="" data-select2-id="ioc_links" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_assets">Save</button>
</div>
</div>
<script>
$('form#form_new_case').validate();
$('#asset_tags').amsifySuggestags({
printValues: false,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ]
});
$('#asset_type_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white",
});
$('#analysis_status_id').selectpicker({
liveSearch: true,
title: "None",
style: "btn-outline-white"
});
$('#analysis_status_id').selectpicker('val', '1');
$('#asset_compromise_status_id').selectpicker({
liveSearch: true,
title: "To be determined",
style: "btn-outline-white"
});
$('#asset_compromise_status_id').selectpicker('val', '0');
</script>
{% if ioc %}
<script>
var data = [
{% for e in ioc %}
{
id: {{ e.ioc_id }},
text: {{ e.ioc_value| tojson }}
},
{% endfor %}
];
$('#ioc_links').select2({ data: data });
</script>
{% endif %}
{% if ioc_prefill %}
<script>
$('#ioc_links').val([
{% for ioc in ioc_prefill %} {{ ioc[0] }}, {% endfor %}
]);
$('#ioc_links').trigger('change');
</script>
{% endif %}

View File

@@ -0,0 +1,98 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{% if rfile.id %}Edit evidence #{{rfile.id}}{% else %}Register evidence{% endif %}</h4>
{% if rfile.id %}<small><i class="text-muted">#{{ rfile.file_uuid }}</i></small>{% endif %}
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
{% if rfile.id %}
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="evidence_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{rfile.id}});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("evidence", {{rfile.id}});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
</div>
</div>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ rfile.id }}, 'evidences')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
{% endif %}
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_rfiles', 'Edit evidence #{{rfile.id}}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_edit_rfile">
<div class="col-md-12 col-lg-12 col-sm-12">
<div class="form-group">
<label for="rfile_filename" class="placeholder">Filename *</label>
<input class="form-control" placeholder="Filename" id="filename" required name="filename" value="{{ rfile.filename }}"/>
</div>
<div class="form-group">
<label for="rfile_size" class="placeholder">File size (bytes) *</label>
<input class="form-control" placeholder="Size in bytes" id="file_size" name="file_size" value="{{ rfile.file_size }}"/>
</div>
<div class="form-group">
<label for="rfile_hash" class="placeholder">File Hash</label>
<input class="form-control" placeholder="Hash" id="file_hash" name="file_hash" value="{{ rfile.file_hash }}"/>
</div>
<div class="form-group">
<label for="rfile_desc" class="placeholder">File description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_evidence_desc();" >
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_evidence_description();" id="evidence_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="evidence_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_evidence_desc_content">
<div id="evidence_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if rfile %}{{ rfile.file_description }}{% endif %}</div>
<textarea id="evidence_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_evidence_description" style="display:none">
<div id="target_evidence_desc"></div>
</div>
</div>
</div>
</div>
<div class="form-group">
<p>Automatically compute file information by selecting it below. The file will not be uploaded nor saved.</p>
<input id="input_autofill" type="file">
<button class="btn btn-sm" type="button" onclick="get_hash()" id="btn_rfile_proc">Process</button>
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
{% if rfile.id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_rfile('{{ rfile.id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
onclick="update_rfile('{{ rfile.id }}');" id="submit_new_rfiles">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success float-right" onclick="add_rfile();">Register</button>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,139 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{% if task.id %} Task ID #{{ task.id }}{% else %} Add task {% endif %}</h4>
<small><i class="text-muted">#{{ task.task_uuid }}</i></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
{% if task.id %}
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="task_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{task.id}});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("task", {{task.id}});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
</div>
</div>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ task.id }}, 'tasks')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
{% endif %}
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_add_task', '{% if task.id %} Task ID #{{ task.id }}{% else %} Add task {% endif %}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div class="container col-md-12">
<form method="post" action="" id="form_new_task">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group mt-3 row">
<div class="col-6 col-xs-12">
<label for="task_assignee_id" class="placeholder">Assigned to *</label>
{{ form.task_assignees_id(class="selectpicker col-12", data_actions_box="true", data_dropup_auto="false") }}
</div>
<div class="col-6 col-xs-12">
<label for="task_status_id" class="placeholder">Status *</label>
{{ form.task_status_id(class="selectpicker col-12") }}
</div>
</div>
<div class="form-group">
<label for="task_title" class="placeholder">{{ form.task_title.label.text }} *</label>
{{ form.task_title(class='form-control col-md-12 col-sm-12', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="task_description" class="placeholder">Description</label>
<div class="md_description_field">
<div class="form-group mt--2">
<button type="button" class="float-right icon-note btn btn-circle btn-sm mt-2" onclick="edit_in_task_desc();">
</button>
<button type="button" style="display: none;" class="btn btn-dark btn-sm float-right mr-2 mt-2"
onclick="preview_task_description();" id="task_preview_button"><i class="fa-solid fa-eye"></i></button>
</div>
<div class="row">
<div class="col mb--2 ml--2" id="task_edition_btn" style="display:none;">
</div>
</div>
<div class="row" style="margin-left:0px;">
<div class="col-12" id="container_task_desc_content">
<div id="task_description" class="mr-2" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{% if task.task_description %}{{ task.task_description }}{% endif %}</div>
<textarea id="task_desc_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-12" id="container_task_description" style="display:none">
<div id="target_task_desc"></div>
</div>
</div>
</div>
</div>
<div class="form-group mt-3">
<label for="task_tags">Task tags
</label>
<input type="text" id="task_tags"
class="form-control col-md-12" {% if task.task_tags %} value="{{ task.task_tags }}" {% endif %}/>
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
</div>
{% if task.id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_task({{ task.id }});">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_task"
onclick="update_task({{ task.id }});">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_task">Save</button>
{% endif %}
</div>
<script>
var data = [];
if (current_users_list.length === 0) {
refresh_users(do_list_users, [{% for assignee in task.task_assignees %} {{ assignee.id }}, {% endfor %}]);
} else {
do_list_users(current_users_list, [{% for assignee in task.task_assignees %} {{ assignee.id }}, {% endfor %}]);
}
$('form#form_new_task').validate();
$('#task_tags').amsifySuggestags({
printValues: false,
suggestions: [ {% for tag in tags %} '{{ tag }}', {% endfor %} ]
});
$('#task_status_id').selectpicker({
liveSearch: true,
title: "Select task status"
});
{% if task.task_status_id %}
$('#task_status_id').selectpicker('val', '{{task.task_status_id}}');
{% else %}
$('#task_status_id').selectpicker('val', 'To do');
{% endif %}
$('[data-toggle="popover"]').popover();
</script>

View File

@@ -0,0 +1,33 @@
<div class="modal-content">
<form method="post" action="" id="form_add_asset">
<div class="modal-header">
<h5>Add asset to graph</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="asset" class="mr-4">Add an existing asset to the case graph.<br>If you want to create a new asset, please go to the Asset tab.
</label>
<select class="selectpicker form-control bg-outline-success dropdown-submenu" data-show-subtext="true" data-live-search="true" id="asset">
{% for asset in assets_list %}
<option value="{{ asset.IP }}">{{ asset.Name }} </option>
{% endfor %}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success float-right" id="submit_add_asset">Add</button>
</div>
</form>
</div><!-- /.modal-content -->
<script>
$('#asset').selectpicker({
liveSearch: true,
title: "None",
style: "Bootstrap 4: 'btn-outline-primary'",
});
</script>

View File

@@ -0,0 +1,54 @@
<link rel="stylesheet" href="/static/assets/css/dropzone.css">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Processing pipelines</h4>
<button type="button" class="pull-right btn btn-white" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
<div class="modal-body">
<div class="container col-md-12">
<div class="row">
<div class="col-12">
<p>Select processing pipeline and drop analysis files below. Press Process to start the processing. Do not close the page until the upload is finished.<br/>
Supports up to 40 files and 10GB at once.</p>
<div class="form-group">
<div class="form-group">
<label for="import_pipeline" class="placeholder">Processing pipeline</label>
<i class="fas fa-question-circle mr-2" data-toggle="popover"
title="Pipelines"
data-content="Pipelines are the way files dropped below are processed. Each pipelines handles a different type of file."></i>
{{ form.pipeline(class="selectpicker pl--6 btn-outline-white", id="update_pipeline_selector") }}
</div>
<div class="form-group col-md-6 mb-2">
{% for itm in pipeline_args %}
{% for tm in itm[2] %}
<div class="input-group mb-4 control-update-pipeline-args control-update-pipeline-{{itm[0]}}">
<div class="input-group-prepend">
<span class="input-group-text">{{tm[0]}} ({{tm[1]}})</span>
</div>
<input class="form-control update-{{itm[0]}}" id="{{tm[0]}}" name="{{tm[0]}}" type="text" value="" {{tm[1]}}>
</div>
{% endfor %}
{% endfor %}
</div>
<div class="tab-content col-md-12">
<div class="dropzone col-md-12" id="files_drop_1">
</div>
</div>
</div>
<span id="msg_submit" class="ml-4"></span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default float-left" data-dismiss="modal">Dismiss</button>
<button type="button" class="btn btn-outline-success float-right"
id="submit_update_case" onclick="submit_update_casefn();">Process</button>
</div>
<script src="/static/assets/js/plugin/dropzone/dropzone.js"></script>
<script src="/static/assets/js/iris/case.pipelines.js"></script>

View File

@@ -0,0 +1,52 @@
<div class="modal-header">
<div style="display:none;" id="current_username">{{current_user.user}}</div>
<div class="col md-12">
<div class="row">
<div class="col-8">
<h4 class="modal-title mt-2 mr-4 text-truncate">Comments on <i>{{ title }}</i></h4>
</div>
<div class="col">
<div class="row float-right">
<button class="btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_comment', 'Comment {{ element_type }}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="float-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body comments-listing">
<div id="comments_list">
</div>
</div>
<div class="modal-footer">
<div class="col">
<div class="row">
<div class="col-12">
<div class="row">
<div class="col mb--2 ml--2" id="comment_edition_btn">
</div>
</div>
<div class="row mb-3">
<div class="col-12 comment-content" id="container_comment_content">
<div id="comment_message" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}"></div>
<textarea id="comment_content" rows="2" style="display: none"></textarea>
</div>
<div class="col-12 comment-content" id="container_comment_preview" style="display:none;">
<div id="target_comment_content"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12" id="container_comment_submit">
<button type="button" class="btn btn-primary btn-sm float-right ml-2" id="comment_submit" onclick="save_comment({{ element_id }}, '{{ element_type }}');"><i class="fa-regular fa-paper-plane"></i> Comment</button>
<button type="button" class="btn btn-primary btn-sm float-right ml-2" id="comment_edition" style="display: none;" onclick="save_edit_comment({{ element_id }}, '{{ element_type }}');"><i class="fa-regular fa-paper-plane"></i> Save</button>
<button type="button" class="btn btn-danger btn-sm float-right ml-2" id="cancel_edition" style="display: none;" onclick="cancel_edition();"><i class="fa-solid fa-xmark"></i> Cancel</button>
<button type="button" class="btn btn-dark btn-sm float-right ml-2" onclick="preview_comment();" id="comment_preview_button"><i class="fa-solid fa-eye"></i> Preview</button>
<button type="button" class="btn btn-light btn-sm float-left" onclick="load_comments({{ element_id }}, '{{ element_type }}', null, true);"><i class="fa-solid fa-refresh"></i> Refresh</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,33 @@
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-1 mr-4">Timeline filtering help</h4>
<button type="button" class="float-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
<div class="modal-body mb-2">
<div class="row">
<div class="col-12">
<p>The timeline can be filtered thanks to simple queries. The query schema is : <code>target_element:search_value AND target_element2:search_value2</code>.<br/>
There is no <code>OR</code> condition and searching without target does not work.
<p>The following target elements can be used to filter :</p>
<ul>
<li><code>asset</code>: Asset linked to the event</li>
<li><code>ioc</code>: IOC linked to the event</li>
<li><code>tag</code>: Tag within the event</li>
<li><code>title</code>: Title of the event</li>
<li><code>description</code>: Description of the event</li>
<li><code>raw</code> : Raw event content</li>
<li><code>category</code>: Category of the event</li>
<li><code>source</code>: Source of the event</li>
<li><code>startDate</code>: Start date to filter with</li>
<li><code>endDate</code>: End date to filter with</li>
</ul>
The dates filters uses the same guessing as the date parser in events, so a lots of format are handled.<br/>
Example of filter :
<code>asset: DESKTOP-X5487 AND description: rdp connection to AND source: Windows Security</code>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,98 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">Note #{{ note.note_id }}</h4>
<small><i class="text-muted">#{{ note.note_uuid }}</i></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col">
<div class="row float-right">
<div class="avatar-group-note mt-2 float-right" id="ppl_list_viewing">
</div>
<button class="btn bg-transparent pull-right" title="Toggle focus mode" id="focus_mode" onclick="toggle_focus_mode();return false;">
<span aria-hidden="true"><i class="fas fas fa-coffee"></i></span>
</button>
<button type="button" class="btn bg-transparent btn-xs" onclick="comment_element({{ note.note_id }}, 'notes')" title="Comments">
<span class="btn-label">
<i class="fa-solid fa-comments"></i><span class="notification" id="object_comments_number">{{ comments_map|length }}</span>
</span>
</button>
<div class="dropdown">
<button class="btn bg-transparent pull-right" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span aria-hidden="true"><i class="fas fa-ellipsis-v"></i></span>
</button>
<div class="dropdown-menu pull-right" id="note_modal_quick_actions" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="#" onclick='copy_object_link({{ note.note_id }});return false;'><i class="fa fa-share mr-2"></i>Share</a>
<a class="dropdown-item" href="#" onclick='copy_object_link_md("note",{{ note.note_id }});return false;'><i class="fa-brands fa-markdown mr-2"></i>Markdown Link</a>
</div>
</div>
<button class="float-right btn bg-transparent" title="Minimize" onclick="modal_minimized('modal_note_detail', '{{ note.title }}');"> <i class='fa fa-minus'></i> </button>
<button type="button" class="pull-right btn bg-transparent" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true"><i class="fa fa-times"></i></span></button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<form method="post" action="" id="form_note">
<iris_notein style="display: none;">{{ note.note_id }}</iris_notein>
{{ note.hidden_tag() }}
<div class="container col-md-12">
<div class="form-group">
<label>Note title *</label>
{{ note.note_title(class='form-control input') }}
</div>
<div class="row mb-1 mt-3">
<div class="col">
<span class="badge badge-light" id="content_typing"></span>
<span class="badge badge-light" id="content_last_saved_by"></span>
</div>
</div>
<div class="row mb-1 mt-3">
<div class="col-10" id="notes_edition_btn">
<div class="btn btn-sm btn-light mr-1 " title="CTRL-S" id="last_saved" onclick="save_note( this );"><i class="fa-solid fa-file-circle-check"></i></div>
<div class="btn btn-sm btn-light mr-1 " title="CTRL-B" onclick="note_editor.insertSnippet('**${1:$SELECTION}**');note_editor.focus();"><i class="fa-solid fa-bold"></i></div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-I" onclick="note_editor.insertSnippet('*${1:$SELECTION}*');note_editor.focus();"><i class="fa-solid fa-italic"></i></div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-1" onclick="note_editor.insertSnippet('# ${1:$SELECTION}');note_editor.focus();">H1</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-2" onclick="note_editor.insertSnippet('## ${1:$SELECTION}');note_editor.focus();">H2</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-3" onclick="note_editor.insertSnippet('### ${1:$SELECTION}');note_editor.focus();">H3</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-SHIFT-4" onclick="note_editor.insertSnippet('#### ${1:$SELECTION}');note_editor.focus();">H4</div>
<div class="btn btn-sm btn-light mr-1" title="CTRL+\`" onclick="note_editor.insertSnippet('```${1:$SELECTION}```');note_editor.focus();"><i class="fa-solid fa-code"></i></div>
<div class="btn btn-sm btn-light mr-1" title="CTRL-K" onclick="note_editor.insertSnippet('[${1:$SELECTION}](url)');note_editor.focus();"><i class="fa-solid fa-link"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert table" onclick="note_editor.insertSnippet('|\t|\t|\t|\n|--|--|--|\n|\t|\t|\t|\n|\t|\t|\t|');note_editor.focus();"><i class="fa-solid fa-table"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert bullet list" onclick="note_editor.insertSnippet('\n- \n- \n- ');note_editor.focus();"><i class="fa-solid fa-list"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Insert numbered list" onclick="note_editor.insertSnippet('\n1. a \n2. b \n3. c ');note_editor.focus();"><i class="fa-solid fa-list-ol"></i></div>
<div class="btn btn-sm btn-light mr-1" title="Toggle editor expansion" onclick="toggle_max_editor();"><i class="fa-solid fa-maximize"></i></div>
<div class="btn btn-sm btn-transparent mr-1" title="Help" onclick="get_md_helper_modal();"><i class="fa-solid fa-question-circle"></i></div>
</div>
<div class="col">
<button type="button" class="float-right icon-note btn btn-circle btn-sm" onclick="edit_innote();"></button>
</div>
</div>
<div class="row">
<div class="col-md-6" id="container_note_content">
<div style="display: none" id="fetched_crc"></div>
<div id="editor_detail" data-theme="{% if current_user.in_dark_mode %}dark{% else %}light{% endif %}">{{ note.content }}</div>
<textarea id="note_content" rows="10" cols="82" style="display: none"></textarea>
</div>
<div class="col-md-6" id="ctrd_notesum">
<div id="targetDiv"></div>
</div>
</div>
</div>
</form>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger mr-auto" onclick="delete_note(this, {{ ncid }});">Delete note</button>
<button type="button" class="btn btn-default" onclick="save_note( this, {{ ncid }} );" id="btn_save_note">Save </button>
</div>

View File

@@ -0,0 +1,24 @@
{% extends "layouts/default.html" %}
{% block title %} Case Error {% endblock title %}
{% block stylesheets %}
{% endblock stylesheets %}
{% block content %}
<br />
<br />
<h2 class="mx-5">No case found for you !</h2><br/><br/>
<h3 class="font-weight-light mx-5">The page youre looking for is only available when a case is selected.</h3><br/>
<h3 class="font-weight-light mx-5">Please press the <b><i class="flaticon-repeat"></i></b> button on the top right to select one.</h3>
{% endblock content %}
{% block javascripts %}
{% endblock javascripts %}