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,97 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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.
from flask import Blueprint
from flask import render_template
from flask import url_for
from flask_wtf import FlaskForm
from werkzeug.utils import redirect
from app.iris_engine.access_control.utils import ac_recompute_all_users_effective_ac
from app.iris_engine.access_control.utils import ac_recompute_effective_ac
from app.iris_engine.access_control.utils import ac_trace_effective_user_permissions
from app.iris_engine.access_control.utils import ac_trace_user_effective_cases_access_2
from app.models.authorization import Permissions
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_success
manage_ac_blueprint = Blueprint(
'access_control',
__name__,
template_folder='templates/access_control'
)
@manage_ac_blueprint.route('/manage/access-control', methods=['GET'])
@ac_requires(Permissions.server_administrator)
def manage_ac_index(caseid, url_redir):
if url_redir:
return redirect(url_for('access_control.manage_ac_index', cid=caseid))
form = FlaskForm()
return render_template("manage_access-control.html", form=form)
@manage_ac_blueprint.route('/manage/access-control/recompute-effective-users-ac', methods=['GET'])
@ac_api_requires(Permissions.server_administrator)
def manage_ac_compute_effective_all_ac(caseid):
ac_recompute_all_users_effective_ac()
return response_success('Updated')
@manage_ac_blueprint.route('/manage/access-control/recompute-effective-user-ac/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator)
def manage_ac_compute_effective_ac(cur_id, caseid):
ac_recompute_effective_ac(cur_id)
return response_success('Updated')
@manage_ac_blueprint.route('/manage/access-control/audit/users/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator)
def manage_ac_audit_user(cur_id, caseid):
user_audit = {
'access_audit': ac_trace_user_effective_cases_access_2(cur_id),
'permissions_audit': ac_trace_effective_user_permissions(cur_id)
}
return response_success(data=user_audit)
@manage_ac_blueprint.route('/manage/access-control/audit/users/<int:cur_id>/modal', methods=['GET'])
@ac_api_requires(Permissions.server_administrator)
def manage_ac_audit_user_modal(cur_id, caseid):
access_audit = ac_trace_user_effective_cases_access_2(cur_id)
permissions_audit = ac_trace_effective_user_permissions(cur_id)
return render_template("modal_user_audit.html", access_audit=access_audit, permissions_audit=permissions_audit)
@manage_ac_blueprint.route('/manage/access-control/audit/users', methods=['GET'])
@ac_requires(Permissions.server_administrator)
def manage_ac_audit_users_page(caseid, url_redir):
form = FlaskForm()
return render_template("manage_user_audit.html", form=form)

View File

@ -0,0 +1,143 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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.
from flask import Blueprint, Response, request
from app.datamgmt.alerts.alerts_db import get_alert_status_list, get_alert_status_by_id, search_alert_status_by_name, \
get_alert_resolution_by_id, get_alert_resolution_list, search_alert_resolution_by_name
from app.schema.marshables import AlertStatusSchema, AlertResolutionSchema
from app.util import ac_api_requires, response_error
from app.util import response_success
manage_alerts_status_blueprint = Blueprint('manage_alerts_status',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_alerts_status_blueprint.route('/manage/alert-status/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_alert_status(caseid: int) -> Response:
"""
Get the list of alert status
Args:
caseid (int): case id
Returns:
Flask Response object
"""
l_cl = get_alert_status_list()
schema = AlertStatusSchema()
return response_success("", data=schema.dump(l_cl, many=True))
@manage_alerts_status_blueprint.route('/manage/alert-status/<int:classification_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_alert_status(classification_id: int, caseid: int) -> Response:
"""
Get the alert status
Args:
status_id (int): status id
caseid (int): case id
"""
cl = get_alert_status_by_id(classification_id)
schema = AlertStatusSchema()
return response_success("", data=schema.dump(cl))
@manage_alerts_status_blueprint.route('/manage/alert-status/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_alert_status(caseid):
if not request.is_json:
return response_error("Invalid request")
alert_status = request.json.get('alert_status')
if alert_status is None:
return response_error("Invalid alert status. Got None")
exact_match = request.json.get('exact_match', False)
# Search for alerts status with a name that contains the specified search term
alert_status = search_alert_status_by_name(alert_status, exact_match=exact_match)
if not alert_status:
return response_error("No alert status found")
# Serialize the alert status and return them in a JSON response
schema = AlertStatusSchema(many=True)
return response_success("", data=schema.dump(alert_status))
@manage_alerts_status_blueprint.route('/manage/alert-resolutions/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_alert_resolution(caseid: int) -> Response:
"""
Get the list of alert resolution
Args:
caseid (int): case id
Returns:
Flask Response object
"""
l_cl = get_alert_resolution_list()
schema = AlertResolutionSchema()
return response_success("", data=schema.dump(l_cl, many=True))
@manage_alerts_status_blueprint.route('/manage/alert-resolutions/<int:resolution_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_alert_resolution(resolution_id: int, caseid: int) -> Response:
"""
Get the alert resolution
Args:
resolution_id (int): resolution id
caseid (int): case id
"""
cl = get_alert_resolution_by_id(resolution_id)
schema = AlertResolutionSchema()
return response_success("", data=schema.dump(cl))
@manage_alerts_status_blueprint.route('/manage/alert-resolutions/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_alert_resolution(caseid):
if not request.is_json:
return response_error("Invalid request")
alert_resolution = request.json.get('alert_resolution_name')
if alert_resolution is None:
return response_error("Invalid alert resolution. Got None")
exact_match = request.json.get('exact_match', False)
# Search for alerts resolution with a name that contains the specified search term
alert_res = search_alert_resolution_by_name(alert_resolution, exact_match=exact_match)
if not alert_res:
return response_error("No alert resolution found")
# Serialize the alert_res and return them in a JSON response
schema = AlertResolutionSchema(many=True)
return response_success("", data=schema.dump(alert_res))

View File

@ -0,0 +1,113 @@
#!/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.
from flask import Blueprint, request
from werkzeug import Response
from app.datamgmt.case.case_assets_db import get_compromise_status_dict, get_case_outcome_status_dict
from app.datamgmt.manage.manage_case_objs import search_analysis_status_by_name
from app.models.models import AnalysisStatus
from app.schema.marshables import AnalysisStatusSchema
from app.util import api_login_required, ac_api_requires
from app.util import response_error
from app.util import response_success
manage_anastatus_blueprint = Blueprint('manage_anastatus',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_anastatus_blueprint.route('/manage/analysis-status/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_anastatus(caseid):
lstatus = AnalysisStatus.query.with_entities(
AnalysisStatus.id,
AnalysisStatus.name
).all()
data = [row._asdict() for row in lstatus]
return response_success("", data=data)
@manage_anastatus_blueprint.route('/manage/compromise-status/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_compr_status(caseid):
compro_status = get_compromise_status_dict()
return response_success("", data=compro_status)
@manage_anastatus_blueprint.route('/manage/outcome-status/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_outcome_status(caseid) -> Response:
""" Returns a list of outcome status
Args:
caseid (int): Case ID
Returns:
Response: Flask response object
"""
outcome_status = get_case_outcome_status_dict()
return response_success("", data=outcome_status)
@manage_anastatus_blueprint.route('/manage/analysis-status/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def view_anastatus(cur_id, caseid):
lstatus = AnalysisStatus.query.with_entities(
AnalysisStatus.id,
AnalysisStatus.name
).filter(
AnalysisStatus.id == cur_id
).first()
if not lstatus:
return response_error(f"Analysis status ID {cur_id} not found")
return response_success("", data=lstatus._asdict())
@manage_anastatus_blueprint.route('/manage/analysis-status/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_analysis_status(caseid):
if not request.is_json:
return response_error("Invalid request")
analysis_status = request.json.get('analysis_status')
if analysis_status is None:
return response_error("Invalid analysis status. Got None")
exact_match = request.json.get('exact_match', False)
# Search for analysis status with a name that contains the specified search term
analysis_status = search_analysis_status_by_name(analysis_status, exact_match=exact_match)
if not analysis_status:
return response_error("No analysis status found")
# Serialize the analysis status and return them in a JSON response
schema = AnalysisStatusSchema(many=True)
return response_success("", data=schema.dump(analysis_status))
# TODO : Add management of analysis status

View File

@ -0,0 +1,249 @@
#!/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 ------------------------------------------------
import logging
import marshmallow
import os
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from app import app
from app import db
from app.datamgmt.manage.manage_case_objs import search_asset_type_by_name
from app.forms import AddAssetForm
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.models.models import AssetsType
from app.models.models import CaseAssets
from app.schema.marshables import AssetTypeSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_assets_blueprint = Blueprint('manage_assets',
__name__,
template_folder='templates')
@manage_assets_blueprint.route('/manage/asset-type/list')
@ac_api_requires(no_cid_required=True)
def list_assets(caseid):
# Get all assets
assets = AssetsType.query.with_entities(
AssetsType.asset_name,
AssetsType.asset_description,
AssetsType.asset_id,
AssetsType.asset_icon_compromised,
AssetsType.asset_icon_not_compromised,
).all()
data = []
for row in assets:
row_dict = row._asdict()
row_dict['asset_icon_compromised_path'] = os.path.join(app.config['ASSET_SHOW_PATH'],row_dict['asset_icon_compromised'])
row_dict['asset_icon_not_compromised_path'] = os.path.join(app.config['ASSET_SHOW_PATH'],row_dict['asset_icon_not_compromised'])
data.append(row_dict)
# data = [row._asdict() for row in assets]
# Return the assets
return response_success("", data=data)
@manage_assets_blueprint.route('/manage/asset-type/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def view_asset_api(cur_id, caseid):
# Get all assets
asset_type = AssetsType.query.with_entities(
AssetsType.asset_name,
AssetsType.asset_description,
AssetsType.asset_id
).filter(
AssetsType.asset_id == cur_id
).first()
if not asset_type:
return response_error(f'Invalid asset type ID {cur_id}')
# Return the assets
return response_success("", data=asset_type._asdict())
@manage_assets_blueprint.route('/manage/asset-type/update/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def view_assets_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_assets.manage_assets', cid=caseid))
form = AddAssetForm()
asset = AssetsType.query.filter(AssetsType.asset_id == cur_id).first()
if not asset:
return response_error("Invalid asset type ID")
form.asset_name.render_kw = {'value': asset.asset_name}
form.asset_description.render_kw = {'value': asset.asset_description}
form.asset_icon_compromised.render_kw = {'value': asset.asset_icon_compromised}
form.asset_icon_not_compromised.render_kw = {'value': asset.asset_icon_not_compromised}
setattr(asset, 'asset_icon_compromised_path', os.path.join(app.config['ASSET_SHOW_PATH'], asset.asset_icon_compromised))
setattr(asset, 'asset_icon_not_compromised_path', os.path.join(app.config['ASSET_SHOW_PATH'], asset.asset_icon_not_compromised))
return render_template("modal_add_asset_type.html", form=form, assettype=asset)
@manage_assets_blueprint.route('/manage/asset-type/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def view_assets(cur_id, caseid):
asset_type = AssetsType.query.filter(AssetsType.asset_id == cur_id).first()
if not asset_type:
return response_error("Invalid asset type ID")
asset_schema = AssetTypeSchema()
try:
asset_sc = asset_schema.load(request.form, instance=asset_type)
fpath_nc = asset_schema.load_store_icon(request.files.get('asset_icon_not_compromised'),
'asset_icon_not_compromised')
fpath_c = asset_schema.load_store_icon(request.files.get('asset_icon_compromised'), 'asset_icon_compromised')
if fpath_nc is not None:
asset_sc.asset_icon_not_compromised = fpath_nc
if fpath_c is not None:
asset_sc.asset_icon_compromised = fpath_c
if asset_sc:
track_activity("updated asset type {}".format(asset_sc.asset_name), caseid=caseid)
return response_success("Asset type updated", asset_sc)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing updated")
@manage_assets_blueprint.route('/manage/asset-type/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_assets_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_assets.manage_assets', cid=caseid))
form = AddAssetForm()
return render_template("modal_add_asset_type.html", form=form, assettype=None)
@manage_assets_blueprint.route('/manage/asset-type/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_assets(caseid):
asset_schema = AssetTypeSchema()
try:
asset_sc = asset_schema.load(request.form)
fpath_nc = asset_schema.load_store_icon(request.files.get('asset_icon_not_compromised'),
'asset_icon_not_compromised')
fpath_c = asset_schema.load_store_icon(request.files.get('asset_icon_compromised'), 'asset_icon_compromised')
if fpath_nc is not None:
asset_sc.asset_icon_not_compromised = fpath_nc
if fpath_c is not None:
asset_sc.asset_icon_compromised = fpath_c
if asset_sc:
db.session.add(asset_sc)
db.session.commit()
track_activity("updated asset type {}".format(asset_sc.asset_name), caseid=caseid)
return response_success("Asset type updated", asset_sc)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing updated")
@manage_assets_blueprint.route('/manage/asset-type/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def delete_assets(cur_id, caseid):
asset = AssetsType.query.filter(AssetsType.asset_id == cur_id).first()
if not asset:
return response_error("Invalid asset ID")
case_linked = CaseAssets.query.filter(CaseAssets.asset_type_id == cur_id).first()
if case_linked:
return response_error("Cannot delete a referenced asset type. Please delete any assets of this type first.")
try:
#not deleting icons for now because multiple asset_types might rely on the same icon
#only delete icons if there is only one AssetType linked to it
if len(AssetsType.query.filter(AssetsType.asset_icon_compromised == asset.asset_icon_compromised).all()) == 1:
os.unlink(os.path.join(app.config['ASSET_STORE_PATH'], asset.asset_icon_compromised))
if len(AssetsType.query.filter(AssetsType.asset_icon_not_compromised == asset.asset_icon_not_compromised).all()) == 1:
os.unlink(os.path.join(app.config['ASSET_STORE_PATH'], asset.asset_icon_not_compromised))
except Exception as e:
logging.error(f"Unable to delete {e}")
db.session.delete(asset)
track_activity("Deleted asset type ID {asset_id}".format(asset_id=cur_id), caseid=caseid, ctx_less=True)
return response_success("Deleted asset type ID {cur_id} successfully".format(cur_id=cur_id))
@manage_assets_blueprint.route('/manage/asset-types/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_assets_type(caseid):
"""Searches for assets types in the database.
This function searches for assets types in the database with a name that contains the specified search term.
It returns a JSON response containing the matching assets types.
Args:
caseid: The ID of the case associated with the request.
Returns:
A JSON response containing the matching assets types.
"""
if not request.is_json:
return response_error("Invalid request")
asset_type = request.json.get('asset_type')
if asset_type is None:
return response_error("Invalid asset type. Got None")
exact_match = request.json.get('exact_match', False)
# Search for assets types with a name that contains the specified search term
assets_type = search_asset_type_by_name(asset_type, exact_match=exact_match)
if not assets_type:
return response_error("No asset types found")
# Serialize the assets types and return them in a JSON response
assetst_schema = AssetTypeSchema(many=True)
return response_success("", data=assetst_schema.dump(assets_type))

View File

@ -0,0 +1,149 @@
#!/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 ------------------------------------------------
import json
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import request
from flask import url_for
from app import db
from app.datamgmt.manage.manage_attribute_db import update_all_attributes
from app.datamgmt.manage.manage_attribute_db import validate_attribute
from app.forms import AddAssetForm
from app.forms import AttributeForm
from app.models.authorization import Permissions
from app.models.models import CustomAttribute
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_attributes_blueprint = Blueprint('manage_attributes',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_attributes_blueprint.route('/manage/attributes')
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_attributes(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_attributes.manage_attributes', cid=caseid))
form = AddAssetForm()
return render_template('manage_attributes.html', form=form)
@manage_attributes_blueprint.route('/manage/attributes/list')
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def list_attributes(caseid):
# Get all attributes
attributes = CustomAttribute.query.with_entities(
CustomAttribute.attribute_id,
CustomAttribute.attribute_content,
CustomAttribute.attribute_display_name,
CustomAttribute.attribute_description,
CustomAttribute.attribute_for
).all()
data = [row._asdict() for row in attributes]
# Return the attributes
return response_success("", data=data)
@manage_attributes_blueprint.route('/manage/attributes/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def attributes_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_attributes.manage_attributes', cid=caseid))
form = AttributeForm()
attribute = CustomAttribute.query.filter(CustomAttribute.attribute_id == cur_id).first()
if not attribute:
return response_error(f"Invalid Attribute ID {cur_id}")
form.attribute_content.data = attribute.attribute_content
return render_template("modal_add_attribute.html", form=form, attribute=attribute)
@manage_attributes_blueprint.route('/manage/attributes/preview', methods=['POST'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def attributes_preview(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_attributes.manage_attributes', cid=caseid))
data = request.get_json()
if not data:
return response_error(f"Invalid request")
attribute = data.get('attribute_content')
if not attribute:
return response_error(f"Invalid request")
try:
attribute = json.loads(attribute)
except Exception as e:
return response_error("Invalid JSON", data=str(e))
templated = render_template("modal_preview_attribute.html", attributes=attribute)
return response_success(data=templated)
@manage_attributes_blueprint.route('/manage/attributes/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_attribute(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request")
attribute = CustomAttribute.query.filter(CustomAttribute.attribute_id == cur_id).first()
if not attribute:
return response_error(f"Invalid Attribute ID {cur_id}")
data = request.get_json()
attr_content = data.get('attribute_content')
if not attr_content:
return response_error("Invalid request")
attr_contents, logs = validate_attribute(attr_content)
if len(logs) > 0:
return response_error("Found errors in attribute", data=logs)
previous_attribute = attribute.attribute_content
attribute.attribute_content = attr_contents
db.session.commit()
# Now try to update every attributes by merging the updated ones
complete_overwrite = data.get('complete_overwrite')
complete_overwrite = complete_overwrite if complete_overwrite else False
partial_overwrite = data.get('partial_overwrite')
partial_overwrite = partial_overwrite if partial_overwrite else False
update_all_attributes(attribute.attribute_for, partial_overwrite=partial_overwrite,
complete_overwrite=complete_overwrite, previous_attribute=previous_attribute)
return response_success("Attribute updated")

View File

@ -0,0 +1,243 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 typing import Union
from flask import Blueprint, Response, url_for, render_template, request
from werkzeug.utils import redirect
from app import db
from app.datamgmt.manage.manage_case_classifications_db import get_case_classifications_list, \
get_case_classification_by_id, search_classification_by_name
from app.forms import CaseClassificationForm
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import CaseClassificationSchema
from app.util import ac_api_requires, response_error, ac_requires
from app.util import response_success
manage_case_classification_blueprint = Blueprint('manage_case_classifications',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_case_classification_blueprint.route('/manage/case-classifications/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_case_classifications(caseid: int) -> Response:
"""Get the list of case classifications
Args:
caseid (int): case id
Returns:
Flask Response object
"""
l_cl = get_case_classifications_list()
return response_success("", data=l_cl)
@manage_case_classification_blueprint.route('/manage/case-classifications/<int:classification_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_classification(classification_id: int, caseid: int) -> Response:
"""Get a case classification
Args:
classification_id (int): case classification id
caseid (int): case id
Returns:
Flask Response object
"""
schema = CaseClassificationSchema()
case_classification = get_case_classification_by_id(classification_id)
if not case_classification:
return response_error(f"Invalid case classification ID {classification_id}")
return response_success("", data=schema.dump(case_classification))
@manage_case_classification_blueprint.route('/manage/case-classifications/update/<int:classification_id>/modal',
methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def update_case_classification_modal(classification_id: int, caseid: int, url_redir: bool) -> Union[str, Response]:
"""Update a case classification
Args:
classification_id (int): case classification id
caseid (int): case id
url_redir (bool): redirect to url
Returns:
Flask Response object or str
"""
if url_redir:
return redirect(url_for('manage_case_classification_blueprint.update_case_classification_modal',
classification_id=classification_id, caseid=caseid))
classification_form = CaseClassificationForm()
case_classification = get_case_classification_by_id(classification_id)
if not case_classification:
return response_error(f"Invalid case classification ID {classification_id}")
classification_form.name.render_kw = {'value': case_classification.name}
classification_form.name_expanded.render_kw = {'value': case_classification.name_expanded}
classification_form.description.render_kw = {'value': case_classification.description}
return render_template("modal_case_classification.html", form=classification_form,
case_classification=case_classification)
@manage_case_classification_blueprint.route('/manage/case-classifications/update/<int:classification_id>',
methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_case_classification(classification_id: int, caseid: int) -> Response:
"""Update a case classification
Args:
classification_id (int): case classification id
caseid (int): case id
Returns:
Flask Response object
"""
if not request.is_json:
return response_error("Invalid request")
case_classification = get_case_classification_by_id(classification_id)
if not case_classification:
return response_error(f"Invalid case classification ID {classification_id}")
ccl = CaseClassificationSchema()
try:
ccls = ccl.load(request.get_json(), instance=case_classification)
if ccls:
track_activity(f"updated case classification {ccls.id}", caseid=caseid)
return response_success("Case classification updated", ccl.dump(ccls))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing updated", data=case_classification)
@manage_case_classification_blueprint.route('/manage/case-classifications/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_case_classification_modal(caseid: int, url_redir: bool) -> Union[str, Response]:
"""Add a case classification
Args:
caseid (int): case id
url_redir (bool): redirect to url
Returns:
Flask Response object or str
"""
if url_redir:
return redirect(url_for('manage_case_classification_blueprint.add_case_classification_modal',
caseid=caseid))
classification_form = CaseClassificationForm()
return render_template("modal_case_classification.html", form=classification_form, case_classification=None)
@manage_case_classification_blueprint.route('/manage/case-classifications/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_case_classification(caseid: int) -> Response:
"""Add a case classification
Args:
caseid (int): case id
Returns:
Flask Response object
"""
if not request.is_json:
return response_error("Invalid request")
ccl = CaseClassificationSchema()
try:
ccls = ccl.load(request.get_json())
if ccls:
db.session.add(ccls)
db.session.commit()
track_activity(f"added case classification {ccls.name}", caseid=caseid)
return response_success("Case classification added", ccl.dump(ccls))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing added", data=None)
@manage_case_classification_blueprint.route('/manage/case-classifications/delete/<int:classification_id>',
methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def delete_case_classification(classification_id: int, caseid: int) -> Response:
"""Delete a case classification
Args:
classification_id (int): case classification id
caseid (int): case id
Returns:
Flask Response object
"""
case_classification = get_case_classification_by_id(classification_id)
if not case_classification:
return response_error(f"Invalid case classification ID {classification_id}")
db.session.delete(case_classification)
db.session.commit()
track_activity(f"deleted case classification {case_classification.name}", caseid=caseid)
return response_success("Case classification deleted")
@manage_case_classification_blueprint.route('/manage/case-classifications/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_alert_status(caseid):
if not request.is_json:
return response_error("Invalid request")
classification_name = request.json.get('classification_name')
if classification_name is None:
return response_error("Invalid classification name. Got None")
exact_match = request.json.get('exact_match', False)
# Search for classifications with a name that contains the specified search term
classification = search_classification_by_name(classification_name, exact_match=exact_match)
if not classification:
return response_error("No classification found")
# Serialize the case classification and return them in a JSON response
schema = CaseClassificationSchema(many=True)
return response_success("", data=schema.dump(classification))

View File

@ -0,0 +1,231 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 typing import Union
from flask import Blueprint, Response, url_for, render_template, request
from werkzeug.utils import redirect
from app import db
from app.datamgmt.manage.manage_case_state_db import get_case_states_list, \
get_case_state_by_id, get_cases_using_state
from app.forms import CaseStateForm
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import CaseStateSchema
from app.util import ac_api_requires, response_error, ac_requires
from app.util import response_success
manage_case_state_blueprint = Blueprint('manage_case_state',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_case_state_blueprint.route('/manage/case-states/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_case_state(caseid: int) -> Response:
"""Get the list of case state
Args:
caseid (int): case id
Returns:
Flask Response object
"""
l_cl = get_case_states_list()
return response_success("", data=l_cl)
@manage_case_state_blueprint.route('/manage/case-states/<int:state_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_state(state_id: int, caseid: int) -> Response:
"""Get a case state
Args:
state_id (int): case state id
caseid (int): case id
Returns:
Flask Response object
"""
schema = CaseStateSchema()
case_state = get_case_state_by_id(state_id)
if not case_state:
return response_error(f"Invalid case state ID {state_id}")
return response_success("", data=schema.dump(case_state))
@manage_case_state_blueprint.route('/manage/case-states/update/<int:state_id>/modal',
methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def update_case_state_modal(state_id: int, caseid: int, url_redir: bool) -> Union[str, Response]:
"""Update a case state
Args:
state_id (int): case state id
caseid (int): case id
url_redir (bool): redirect to url
Returns:
Flask Response object or str
"""
if url_redir:
return redirect(url_for('manage_case_state_blueprint.update_case_state_modal',
state_id=state_id, caseid=caseid))
state_form = CaseStateForm()
case_state = get_case_state_by_id(state_id)
if not case_state:
return response_error(f"Invalid case state ID {state_id}")
state_form.state_name.render_kw = {'value': case_state.state_name}
state_form.state_description.render_kw = {'value': case_state.state_description}
return render_template("modal_case_state.html", form=state_form,
case_state=case_state)
@manage_case_state_blueprint.route('/manage/case-states/update/<int:state_id>',
methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_case_state(state_id: int, caseid: int) -> Response:
"""Update a case state
Args:
state_id (int): case state id
caseid (int): case id
Returns:
Flask Response object
"""
if not request.is_json:
return response_error("Invalid request")
case_state = get_case_state_by_id(state_id)
if not case_state:
return response_error(f"Invalid case state ID {state_id}")
if case_state.protected:
return response_error(f"Case state {case_state.state_name} is protected")
ccl = CaseStateSchema()
try:
ccls = ccl.load(request.get_json(), instance=case_state)
if ccls:
track_activity(f"updated case state {ccls.state_id}", caseid=caseid)
return response_success("Case state updated", ccl.dump(ccls))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing updated", data=case_state)
@manage_case_state_blueprint.route('/manage/case-states/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_case_state_modal(caseid: int, url_redir: bool) -> Union[str, Response]:
"""Add a case state
Args:
caseid (int): case id
url_redir (bool): redirect to url
Returns:
Flask Response object or str
"""
if url_redir:
return redirect(url_for('manage_case_state_blueprint.add_case_state_modal',
caseid=caseid))
state_form = CaseStateForm()
return render_template("modal_case_state.html", form=state_form, case_state=None)
@manage_case_state_blueprint.route('/manage/case-states/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_case_state(caseid: int) -> Response:
"""Add a case state
Args:
caseid (int): case id
Returns:
Flask Response object
"""
if not request.is_json:
return response_error("Invalid request")
ccl = CaseStateSchema()
try:
ccls = ccl.load(request.get_json())
if ccls:
db.session.add(ccls)
db.session.commit()
track_activity(f"added case state {ccls.state_name}", caseid=caseid)
return response_success("Case state added", ccl.dump(ccls))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing added", data=None)
@manage_case_state_blueprint.route('/manage/case-states/delete/<int:state_id>',
methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def delete_case_state(state_id: int, caseid: int) -> Response:
"""Delete a case state
Args:
state_id (int): case state id
caseid (int): case id
Returns:
Flask Response object
"""
case_state = get_case_state_by_id(state_id)
if not case_state:
return response_error(f"Invalid case state ID {state_id}")
if case_state.protected:
return response_error(f"Case state {case_state.state_name} is protected")
cases = get_cases_using_state(case_state.state_id)
if cases:
return response_error(f"Case state {case_state.state_name} is in use by case(s)"
f" {','.join([str(c.case_id) for c in cases])}")
db.session.delete(case_state)
db.session.commit()
track_activity(f"deleted case state {case_state.state_name}", caseid=caseid)
return response_success("Case state deleted")

View File

@ -0,0 +1,251 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 ------------------------------------------------
import json
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 marshmallow import ValidationError
from app import db
from app.datamgmt.manage.manage_case_templates_db import get_case_templates_list
from app.datamgmt.manage.manage_case_templates_db import get_case_template_by_id
from app.datamgmt.manage.manage_case_templates_db import validate_case_template
from app.datamgmt.manage.manage_case_templates_db import delete_case_template_by_id
from app.forms import CaseTemplateForm, AddAssetForm
from app.models import CaseTemplate
from app.models.authorization import Permissions
from app.iris_engine.utils.tracker import track_activity
from app.schema.marshables import CaseTemplateSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_case_templates_blueprint = Blueprint('manage_case_templates',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_case_templates_blueprint.route('/manage/case-templates', methods=['GET'])
@ac_requires(Permissions.case_templates_read)
def manage_case_templates(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_case_templates.manage_case_templates', cid=caseid))
form = AddAssetForm()
return render_template('manage_case_templates.html', form=form)
@manage_case_templates_blueprint.route('/manage/case-templates/list', methods=['GET'])
@ac_api_requires()
def list_case_templates(caseid):
"""Show a list of case templates
Returns:
Response: List of case templates
"""
case_templates = get_case_templates_list()
# Return the attributes
return response_success("", data=case_templates)
@manage_case_templates_blueprint.route('/manage/case-templates/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.case_templates_read)
def case_template_modal(cur_id, caseid, url_redir):
"""Get a case template
Args:
cur_id (int): case template id
Returns:
HTML Template: Case template modal
"""
if url_redir:
return redirect(url_for('manage_case_templates.manage_case_templates', cid=caseid))
form = CaseTemplateForm()
case_template = get_case_template_by_id(cur_id)
if not case_template:
return response_error(f"Invalid Case template ID {cur_id}")
# Temporary : for now we build the full JSON form object based on case templates attributes
# Next step : add more fields to the form
case_template_dict = {
"name": case_template.name,
"display_name": case_template.display_name,
"description": case_template.description,
"author": case_template.author,
"title_prefix": case_template.title_prefix,
"summary": case_template.summary,
"tags": case_template.tags,
"tasks": case_template.tasks,
"note_groups": case_template.note_groups,
"classification": case_template.classification
}
form.case_template_json.data = case_template_dict
return render_template("modal_case_template.html", form=form, case_template=case_template)
@manage_case_templates_blueprint.route('/manage/case-templates/add/modal', methods=['GET'])
@ac_api_requires(Permissions.case_templates_write)
def add_template_modal(caseid):
case_template = CaseTemplate()
form = CaseTemplateForm()
form.case_template_json.data = {
"name": "Template name",
"display_name": "Template Display Name",
"description": "Template description",
"author": "YOUR NAME",
"classification": "known-template-classification",
"title_prefix": "[PREFIX]",
"summary": "Summary to be set",
"tags": ["ransomware","malware"],
"tasks": [
{
"title": "Task 1",
"description": "Task 1 description",
"tags": ["tag1", "tag2"]
}
],
"note_groups": [
{
"title": "Note group 1",
"notes": [
{
"title": "Note 1",
"content": "Note 1 content"
}
]
}
]
}
return render_template("modal_case_template.html", form=form, case_template=case_template)
@manage_case_templates_blueprint.route('/manage/case-templates/upload/modal', methods=['GET'])
@ac_api_requires(Permissions.case_templates_write)
def upload_template_modal(caseid):
return render_template("modal_upload_case_template.html")
@manage_case_templates_blueprint.route('/manage/case-templates/add', methods=['POST'])
@ac_api_requires(Permissions.case_templates_write)
def add_case_template(caseid):
data = request.get_json()
if not data:
return response_error("Invalid request")
case_template_json = data.get('case_template_json')
if not case_template_json:
return response_error("Invalid request")
try:
case_template_dict = json.loads(case_template_json)
except Exception as e:
return response_error("Invalid JSON", data=str(e))
try:
logs = validate_case_template(case_template_dict, update=False)
if logs is not None:
return response_error("Found errors in case template", data=logs)
except Exception as e:
return response_error("Found errors in case template", data=str(e))
try:
case_template_dict["created_by_user_id"] = current_user.id
case_template_data = CaseTemplateSchema().load(case_template_dict)
case_template = CaseTemplate(**case_template_data)
db.session.add(case_template)
db.session.commit()
except Exception as e:
return response_error("Could not add case template into DB", data=str(e))
track_activity(f"Case template '{case_template.name}' added", caseid=caseid, ctx_less=True)
return response_success("Added successfully", data=CaseTemplateSchema().dump(case_template))
@manage_case_templates_blueprint.route('/manage/case-templates/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.case_templates_write)
def update_case_template(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request")
case_template = get_case_template_by_id(cur_id)
if not case_template:
return response_error(f"Invalid Case template ID {cur_id}")
data = request.get_json()
updated_case_template_json = data.get('case_template_json')
if not updated_case_template_json:
return response_error("Invalid request")
try:
updated_case_template_dict = json.loads(updated_case_template_json)
except Exception as e:
return response_error("Invalid JSON", data=str(e))
try:
logs = validate_case_template(updated_case_template_dict, update=True)
if logs is not None:
return response_error("Found errors in case template", data=logs)
except Exception as e:
return response_error("Found errors in case template", data=str(e))
case_template_schema = CaseTemplateSchema()
try:
# validate the request data and load it into an instance of the `CaseTemplate` object
case_template_data = case_template_schema.load(updated_case_template_dict, partial=True)
# update the existing `case_template` object with the new data
case_template.update_from_dict(case_template_data)
# commit the changes to the database
db.session.commit()
except ValidationError as error:
return response_error("Could not validate case template", data=str(error))
return response_success("Case template updated")
@manage_case_templates_blueprint.route('/manage/case-templates/delete/<int:case_template_id>', methods=['POST'])
@ac_api_requires(Permissions.case_templates_write)
def delete_case_template(case_template_id, caseid):
case_template = get_case_template_by_id(case_template_id)
if case_template is None:
return response_error('Case template not found')
case_template_name = case_template.name
delete_case_template_by_id(case_template_id)
db.session.commit()
track_activity(f"Case template '{case_template_name}' deleted", caseid=caseid, ctx_less=True)
return response_success("Deleted successfully")

View File

@ -0,0 +1,568 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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.
from typing import Union
import logging as log
# IMPORTS ------------------------------------------------
import os
import traceback
import urllib.parse
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 werkzeug import Response
import app
from app import db
from app.datamgmt.alerts.alerts_db import get_alert_status_by_name
from app.datamgmt.case.case_db import get_case, get_review_id_from_name
from app.datamgmt.case.case_db import register_case_protagonists
from app.datamgmt.case.case_db import save_case_tags
from app.datamgmt.client.client_db import get_client_list
from app.datamgmt.iris_engine.modules_db import get_pipelines_args_from_name
from app.datamgmt.iris_engine.modules_db import iris_module_exists
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.manage.manage_case_classifications_db import get_case_classifications_list
from app.datamgmt.manage.manage_case_state_db import get_case_states_list, get_case_state_by_name
from app.datamgmt.manage.manage_case_templates_db import get_case_templates_list, case_template_pre_modifier, \
case_template_post_modifier
from app.datamgmt.manage.manage_cases_db import close_case, map_alert_resolution_to_case_status
from app.datamgmt.manage.manage_cases_db import delete_case
from app.datamgmt.manage.manage_cases_db import get_case_details_rt
from app.datamgmt.manage.manage_cases_db import get_case_protagonists
from app.datamgmt.manage.manage_cases_db import list_cases_dict
from app.datamgmt.manage.manage_cases_db import reopen_case
from app.datamgmt.manage.manage_users_db import get_user_organisations
from app.forms import AddCaseForm
from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
from app.iris_engine.access_control.utils import ac_fast_check_user_has_case_access
from app.iris_engine.access_control.utils import ac_set_new_case_access
from app.iris_engine.module_handler.module_handler import call_modules_hook
from app.iris_engine.module_handler.module_handler import configure_module_on_init
from app.iris_engine.module_handler.module_handler import instantiate_module_from_name
from app.iris_engine.tasker.tasks import task_case_update
from app.iris_engine.utils.common import build_upload_path
from app.iris_engine.utils.tracker import track_activity
from app.models.alerts import AlertStatus
from app.models.authorization import CaseAccessLevel
from app.models.authorization import Permissions
from app.models.models import Client, ReviewStatusList
from app.schema.marshables import CaseSchema
from app.util import ac_api_case_requires, add_obj_history_entry
from app.util import ac_api_requires
from app.util import ac_api_return_access_denied
from app.util import ac_case_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_cases_blueprint = Blueprint('manage_case',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_cases_blueprint.route('/manage/cases', methods=['GET'])
@ac_requires(Permissions.standard_user, no_cid_required=True)
def manage_index_cases(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_case.manage_index_cases', cid=caseid))
form = AddCaseForm()
# Fill select form field customer with the available customers in DB
form.case_customer.choices = [(c.client_id, c.name) for c in
Client.query.order_by(Client.name)]
form.case_organisations.choices = [(org['org_id'], org['org_name']) for org in
get_user_organisations(current_user.id)]
form.classification_id.choices = [(clc['id'], clc['name_expanded']) for clc in get_case_classifications_list()]
form.case_template_id.choices = [(ctp['id'], ctp['display_name']) for ctp in get_case_templates_list()]
attributes = get_default_custom_attributes('case')
return render_template('manage_cases.html', form=form, attributes=attributes)
@manage_cases_blueprint.route('/manage/cases/details/<int:cur_id>', methods=['GET'])
@ac_requires(no_cid_required=True)
def details_case(cur_id: int, caseid: int, url_redir: bool) -> Union[Response, str]:
"""
Get case details
Args:
cur_id (int): case id
caseid (int): case id
url_redir (bool): url redirection
Returns:
Union[str, Response]: The case details
"""
if url_redir:
return response_error("Invalid request")
if not ac_fast_check_user_has_case_access(current_user.id, cur_id, [CaseAccessLevel.read_only,
CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
res = get_case_details_rt(cur_id)
case_classifications = get_case_classifications_list()
case_states = get_case_states_list()
customers = get_client_list()
form = FlaskForm()
if res:
return render_template("modal_case_info_from_case.html", data=res, form=form, protagnists=None,
case_classifications=case_classifications, case_states=case_states, customers=customers)
else:
return response_error("Unknown case")
@manage_cases_blueprint.route('/case/details/<int:cur_id>', methods=['GET'])
@ac_requires(no_cid_required=True)
def details_case_from_case_modal(cur_id: int, caseid: int, url_redir: bool) -> Union[str, Response]:
""" Returns the case details modal for a case from a case
Args:
cur_id (int): The case id
caseid (int): The case id
url_redir (bool): If the request is a url redirect
Returns:
Union[str, Response]: The case details modal
"""
if url_redir:
return response_error("Invalid request")
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.read_only, CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
res = get_case_details_rt(cur_id)
case_classifications = get_case_classifications_list()
case_states = get_case_states_list()
customers = get_client_list()
protagonists = get_case_protagonists(cur_id)
form = FlaskForm()
if res:
return render_template("modal_case_info_from_case.html", data=res, form=form, protagonists=protagonists,
case_classifications=case_classifications, case_states=case_states, customers=customers)
else:
return response_error("Unknown case")
@manage_cases_blueprint.route('/manage/cases/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_api(cur_id, caseid):
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.read_only, CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
res = get_case_details_rt(cur_id)
if res:
return response_success(data=res)
return response_error(f'Case ID {cur_id} not found')
@manage_cases_blueprint.route('/manage/cases/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def api_delete_case(cur_id, caseid):
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
if cur_id == 1:
track_activity("tried to delete case {}, but case is the primary case".format(cur_id),
caseid=caseid, ctx_less=True)
return response_error("Cannot delete a primary case to keep consistency")
else:
try:
call_modules_hook('on_preload_case_delete', data=cur_id, caseid=caseid)
if delete_case(case_id=cur_id):
call_modules_hook('on_postload_case_delete', data=cur_id, caseid=caseid)
track_activity("case {} deleted successfully".format(cur_id), ctx_less=True)
return response_success("Case successfully deleted")
else:
track_activity("tried to delete case {}, but it doesn't exist".format(cur_id),
caseid=caseid, ctx_less=True)
return response_error("Tried to delete a non-existing case")
except Exception as e:
app.app.logger.exception(e)
return response_error("Cannot delete the case. Please check server logs for additional informations")
@manage_cases_blueprint.route('/manage/cases/reopen/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def api_reopen_case(cur_id, caseid):
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
if not cur_id:
return response_error("No case ID provided")
case = get_case(cur_id)
if not case:
return response_error("Tried to reopen an non-existing case")
res = reopen_case(cur_id)
if not res:
return response_error("Tried to reopen an non-existing case")
# Reopen the related alerts
if case.alerts:
merged_status = get_alert_status_by_name('Merged')
for alert in case.alerts:
if alert.alert_status_id != merged_status.status_id:
alert.alert_status_id = merged_status.status_id
track_activity(f"alert ID {alert.alert_id} status updated to merged due to case #{caseid} being reopen",
caseid=caseid, ctx_less=False)
db.session.add(alert)
case = call_modules_hook('on_postload_case_update', data=case, caseid=caseid)
add_obj_history_entry(case, 'case reopen')
track_activity("reopen case ID {}".format(cur_id), caseid=caseid)
case_schema = CaseSchema()
return response_success("Case reopen successfully", data=case_schema.dump(res))
@manage_cases_blueprint.route('/manage/cases/close/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def api_case_close(cur_id, caseid):
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
if not cur_id:
return response_error("No case ID provided")
case = get_case(cur_id)
if not case:
return response_error("Tried to close an non-existing case")
res = close_case(cur_id)
if not res:
return response_error("Tried to close an non-existing case")
# Close the related alerts
if case.alerts:
close_status = get_alert_status_by_name('Closed')
case_status_id_mapped = map_alert_resolution_to_case_status(case.status_id)
for alert in case.alerts:
if alert.alert_status_id != close_status.status_id:
alert.alert_status_id = close_status.status_id
alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=caseid)
if alert.alert_resolution_status_id != case_status_id_mapped:
alert.alert_resolution_status_id = case_status_id_mapped
alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, caseid=caseid)
track_activity(f"closing alert ID {alert.alert_id} due to case #{caseid} being closed",
caseid=caseid, ctx_less=False)
db.session.add(alert)
case = call_modules_hook('on_postload_case_update', data=case, caseid=caseid)
add_obj_history_entry(case, 'case closed')
track_activity("closed case ID {}".format(cur_id), caseid=caseid, ctx_less=False)
case_schema = CaseSchema()
return response_success("Case closed successfully", data=case_schema.dump(res))
@manage_cases_blueprint.route('/manage/cases/add', methods=['POST'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def api_add_case(caseid):
case_schema = CaseSchema()
try:
request_data = call_modules_hook('on_preload_case_create', data=request.get_json(), caseid=caseid)
case_template_id = request_data.pop("case_template_id", None)
case = case_schema.load(request_data)
case.owner_id = current_user.id
if case_template_id and len(case_template_id) > 0:
case = case_template_pre_modifier(case, case_template_id)
if case is None:
return response_error(msg=f"Invalid Case template ID {case_template_id}", status=400)
case.state_id = get_case_state_by_name('Open').state_id
case.save()
if case_template_id and len(case_template_id) > 0:
try:
case, logs = case_template_post_modifier(case, case_template_id)
if len(logs) > 0:
return response_error(msg=f"Could not update new case with {case_template_id}", data=logs, status=400)
except Exception as e:
log.error(e.__str__())
return response_error(msg=f"Unexpected error when loading template {case_template_id} to new case.",
status=400)
ac_set_new_case_access(None, case.case_id)
case = call_modules_hook('on_postload_case_create', data=case, caseid=caseid)
add_obj_history_entry(case, 'created')
track_activity("new case {case_name} created".format(case_name=case.name), caseid=case.case_id, ctx_less=False)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
except Exception as e:
log.error(e.__str__())
log.error(traceback.format_exc())
return response_error(msg="Error creating case - check server logs", status=400)
return response_success(msg='Case created', data=case_schema.dump(case))
@manage_cases_blueprint.route('/manage/cases/list', methods=['GET'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def api_list_case(caseid):
data = list_cases_dict(current_user.id)
return response_success("", data=data)
@manage_cases_blueprint.route('/manage/cases/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.standard_user, no_cid_required=True)
def update_case_info(cur_id, caseid):
if not ac_fast_check_current_user_has_case_access(cur_id, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=cur_id)
case_schema = CaseSchema()
case_i = get_case(cur_id)
if not case_i:
return response_error("Case not found")
try:
request_data = request.get_json()
previous_case_state = case_i.state_id
case_previous_reviewer_id = case_i.reviewer_id
closed_state_id = get_case_state_by_name('Closed').state_id
request_data['case_name'] = f"#{case_i.case_id} - {request_data.get('case_name').replace(f'#{case_i.case_id} - ', '')}"
request_data['case_customer'] = case_i.client_id if request_data.get('case_customer') is None else request_data.get('case_customer')
request_data['reviewer_id'] = None if request_data.get('reviewer_id') == "" else request_data.get('reviewer_id')
case = case_schema.load(request_data, instance=case_i, partial=True)
db.session.commit()
if previous_case_state != case.state_id:
if case.state_id == closed_state_id:
track_activity("case closed", caseid=cur_id)
res = close_case(cur_id)
if not res:
return response_error("Tried to close an non-existing case")
# Close the related alerts
if case.alerts:
close_status = get_alert_status_by_name('Closed')
case_status_id_mapped = map_alert_resolution_to_case_status(case.status_id)
for alert in case.alerts:
if alert.alert_status_id != close_status.status_id:
alert.alert_status_id = close_status.status_id
alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=caseid)
if alert.alert_resolution_status_id != case_status_id_mapped:
alert.alert_resolution_status_id = case_status_id_mapped
alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, caseid=caseid)
track_activity(f"closing alert ID {alert.alert_id} due to case #{caseid} being closed",
caseid=caseid, ctx_less=False)
db.session.add(alert)
elif previous_case_state == closed_state_id and case.state_id != closed_state_id:
track_activity("case re-opened", caseid=cur_id)
res = reopen_case(cur_id)
if not res:
return response_error("Tried to re-open an non-existing case")
if case_previous_reviewer_id != case.reviewer_id:
if case.reviewer_id is None:
track_activity("case reviewer removed", caseid=cur_id)
case.review_status_id = get_review_id_from_name(ReviewStatusList.not_reviewed)
else:
track_activity("case reviewer changed", caseid=cur_id)
register_case_protagonists(case.case_id, request_data.get('protagonists'))
save_case_tags(request_data.get('case_tags'), case_i)
case = call_modules_hook('on_postload_case_update', data=case, caseid=caseid)
add_obj_history_entry(case_i, 'case info updated')
track_activity("case updated {case_name}".format(case_name=case.name), caseid=cur_id)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
except Exception as e:
log.error(e.__str__())
log.error(traceback.format_exc())
return response_error(msg="Error updating case - check server logs", status=400)
return response_success(msg='Case updated', data=case_schema.dump(case))
@manage_cases_blueprint.route('/manage/cases/trigger-pipeline', methods=['POST'])
@ac_api_requires(Permissions.standard_user)
def update_case_files(caseid):
if not ac_fast_check_current_user_has_case_access(caseid, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=caseid)
# case update request. The files should have already arrived with the request upload_files
try:
# Create the update task
jsdata = request.get_json()
if not jsdata:
return response_error('Not a JSON content', status=400)
pipeline = jsdata.get('pipeline')
try:
pipeline_mod = pipeline.split("-")[0]
pipeline_name = pipeline.split("-")[1]
except Exception as e:
log.error(e.__str__())
return response_error('Malformed request', status=400)
ppl_config = get_pipelines_args_from_name(pipeline_mod)
if not ppl_config:
return response_error('Malformed request', status=400)
pl_args = ppl_config['pipeline_args']
pipeline_args = {}
for argi in pl_args:
arg = argi[0]
fetch_arg = jsdata.get('args_' + arg)
if argi[1] == 'required' and (not fetch_arg or fetch_arg == ""):
return response_error("Required arguments are not set")
if fetch_arg:
pipeline_args[arg] = fetch_arg
else:
pipeline_args[arg] = None
status = task_case_update(
module=pipeline_mod,
pipeline=pipeline_name,
pipeline_args=pipeline_args,
caseid=caseid)
if status.is_success():
# The job has been created, so return. The progress can be followed on the dashboard
return response_success("Case task created")
else:
# We got some errors and cannot continue
return response_error(status.get_message(), data=status.get_data())
except Exception as e:
traceback.print_exc()
return response_error('Fail to update case', data=traceback.print_exc())
@manage_cases_blueprint.route('/manage/cases/upload_files', methods=['POST'])
@ac_api_requires(Permissions.standard_user)
def manage_cases_uploadfiles(caseid):
"""
Handles the entire the case management, i.e creation, update, list and files imports
:param path: Path within the URL
:return: Depends on the path, either a page a JSON
"""
if not ac_fast_check_current_user_has_case_access(caseid, [CaseAccessLevel.full_access]):
return ac_api_return_access_denied(caseid=caseid)
# Files uploads of a case. Get the files, create the folder tree
# The request "add" will start the check + import of the files.
f = request.files.get('file')
is_update = request.form.get('is_update', type=str)
pipeline = request.form.get('pipeline', '', type=str)
try:
pipeline_mod = pipeline.split("-")[0]
except Exception as e:
log.error(e.__str__())
return response_error('Malformed request', status=400)
if not iris_module_exists(pipeline_mod):
return response_error('Missing pipeline', status=400)
mod, _ = instantiate_module_from_name(pipeline_mod)
status = configure_module_on_init(mod)
if status.is_failure():
return response_error("Path for upload {} is not built ! Unreachable pipeline".format(
os.path.join(f.filename)), status=400)
case_customer = None
case_name = None
if is_update == "true":
case = get_case(caseid)
if case:
case_name = case.name
case_customer = case.client.name
else:
case_name = urllib.parse.quote(request.form.get('case_name', '', type=str), safe='')
case_customer = request.form.get('case_customer', type=str)
fpath = build_upload_path(case_customer=case_customer,
case_name=urllib.parse.unquote(case_name),
module=pipeline_mod,
create=is_update
)
status = mod.pipeline_files_upload(fpath, f, case_customer, case_name, is_update)
if status.is_success():
return response_success(status.get_message())
return response_error(status.get_message(), status=400)

View File

@ -0,0 +1,404 @@
#!/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 ------------------------------------------------
import datetime
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_wtf import FlaskForm
from marshmallow import ValidationError
from app.datamgmt.client.client_db import create_client
from app.datamgmt.client.client_db import create_contact
from app.datamgmt.client.client_db import delete_client
from app.datamgmt.client.client_db import delete_contact
from app.datamgmt.client.client_db import get_client
from app.datamgmt.client.client_db import get_client_api
from app.datamgmt.client.client_db import get_client_cases
from app.datamgmt.client.client_db import get_client_contact
from app.datamgmt.client.client_db import get_client_contacts
from app.datamgmt.client.client_db import get_client_list
from app.datamgmt.client.client_db import update_client
from app.datamgmt.client.client_db import update_contact
from app.datamgmt.exceptions.ElementExceptions import ElementInUseException
from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.forms import AddCustomerForm
from app.forms import ContactForm
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import ContactSchema
from app.schema.marshables import CustomerSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import page_not_found
from app.util import response_error
from app.util import response_success
manage_customers_blueprint = Blueprint(
'manage_customers',
__name__,
template_folder='templates'
)
# CONTENT ------------------------------------------------
@manage_customers_blueprint.route('/manage/customers')
@ac_requires(Permissions.customers_read, no_cid_required=True)
def manage_customers(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
form = AddCustomerForm()
# Return default page of case management
return render_template('manage_customers.html', form=form)
@manage_customers_blueprint.route('/manage/customers/list')
@ac_api_requires(Permissions.customers_read, no_cid_required=True)
def list_customers(caseid):
client_list = get_client_list()
return response_success("", data=client_list)
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.customers_read, no_cid_required=True)
def view_customer(cur_id, caseid):
customer = get_client_api(cur_id)
if not customer:
return page_not_found(None)
contacts = get_client_contacts(cur_id)
contacts = ContactSchema().dump(contacts, many=True)
customer['contacts'] = contacts
return response_success(data=customer)
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/view', methods=['GET'])
@ac_requires(Permissions.customers_read, no_cid_required=True)
def view_customer_page(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
customer = get_client_api(cur_id)
if not customer:
return page_not_found(None)
form = FlaskForm()
contacts = get_client_contacts(cur_id)
contacts = ContactSchema().dump(contacts, many=True)
return render_template('manage_customer_view.html', customer=customer, form=form, contacts=contacts)
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/contacts/add/modal', methods=['GET'])
@ac_requires(Permissions.customers_write, no_cid_required=True)
def customer_add_contact_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
form = ContactForm()
return render_template('modal_customer_add_contact.html', form=form, contact=None)
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/contacts/<int:contact_id>/modal', methods=['GET'])
@ac_requires(Permissions.customers_read, no_cid_required=True)
def customer_edit_contact_modal(cur_id, contact_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
contact = get_client_contact(cur_id, contact_id)
if not contact:
return response_error(f"Invalid Contact ID {contact_id}")
form = ContactForm()
form.contact_name.render_kw = {'value': contact.contact_name}
form.contact_email.render_kw = {'value': contact.contact_email}
form.contact_mobile_phone.render_kw = {'value': contact.contact_mobile_phone}
form.contact_work_phone.render_kw = {'value': contact.contact_work_phone}
form.contact_note.data = contact.contact_note
form.contact_role.render_kw = {'value': contact.contact_role}
return render_template('modal_customer_add_contact.html', form=form, contact=contact)
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/contacts/<int:contact_id>/update', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def customer_update_contact(cur_id, contact_id, caseid):
if not request.is_json:
return response_error("Invalid request")
if not get_client(cur_id):
return response_error(f"Invalid Customer ID {cur_id}")
try:
contact = update_contact(request.json, contact_id, cur_id)
except ValidationError as e:
return response_error(msg='Error update contact', data=e.messages, status=400)
except Exception as e:
print(traceback.format_exc())
return response_error(f'An error occurred during contact update. {e}')
track_activity(f"Updated contact {contact.contact_name}", caseid=caseid, ctx_less=True)
# Return the customer
contact_schema = ContactSchema()
return response_success("Added successfully", data=contact_schema.dump(contact))
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/contacts/add', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def customer_add_contact(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request")
if not get_client(cur_id):
return response_error(f"Invalid Customer ID {cur_id}")
try:
contact = create_contact(request.json, cur_id)
except ValidationError as e:
return response_error(msg='Error adding contact', data=e.messages, status=400)
except Exception as e:
print(traceback.format_exc())
return response_error(f'An error occurred during contact addition. {e}')
track_activity(f"Added contact {contact.contact_name}", caseid=caseid, ctx_less=True)
# Return the customer
contact_schema = ContactSchema()
return response_success("Added successfully", data=contact_schema.dump(contact))
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/cases', methods=['GET'])
@ac_api_requires(Permissions.customers_read, no_cid_required=True)
def get_customer_case_stats(cur_id, caseid):
cases = get_client_cases(cur_id)
cases_list = []
now = datetime.date.today()
cases_stats = {
'cases_last_month': 0,
'cases_last_year': 0,
'open_cases': 0,
'last_year': now.year - 1,
'last_month': now.month - 1,
'cases_rolling_week': 0,
'cases_current_month': 0,
'cases_current_year': 0,
'ratio_year': 0,
'ratio_month': 0,
'average_case_duration': 0,
'cases_total': len(cases)
}
last_month_start = datetime.date.today() - datetime.timedelta(days=30)
last_month_end = datetime.date(now.year, now.month, 1)
last_year_start = datetime.date(now.year - 1, 1, 1)
last_year_end = datetime.date(now.year - 1, 12, 31)
this_year_start = datetime.date(now.year, 1, 1)
this_month_start = datetime.date(now.year, now.month, 1)
for case in cases:
cases_list.append(case._asdict())
if now - datetime.timedelta(days=7) <= case.open_date <= now:
cases_stats['cases_rolling_week'] += 1
if this_month_start <= case.open_date <= now:
cases_stats['cases_current_month'] += 1
if this_year_start <= case.open_date <= now:
cases_stats['cases_current_year'] += 1
if last_month_start < case.open_date < last_month_end:
cases_stats['cases_last_month'] += 1
if last_year_start <= case.open_date <= last_year_end:
cases_stats['cases_last_year'] += 1
if case.close_date is None:
cases_stats['open_cases'] += 1
if cases_stats['cases_last_year'] == 0:
st = 1
et = cases_stats['cases_current_year'] + 1
else:
st = cases_stats['cases_last_year']
et = cases_stats['cases_current_year']
cases_stats['ratio_year'] = ((et - st)/(st)) * 100
if cases_stats['cases_last_month'] == 0:
st = 1
et = cases_stats['cases_current_month'] + 1
else:
st = cases_stats['cases_last_month']
et = cases_stats['cases_current_month']
cases_stats['ratio_month'] = ((et - st)/(st)) * 100
if (case.close_date is not None) and (case.open_date is not None):
cases_stats['average_case_duration'] += (case.close_date - case.open_date).days
if cases_stats['cases_total'] > 0 and cases_stats['open_cases'] > 0 and cases_stats['average_case_duration'] > 0:
cases_stats['average_case_duration'] = cases_stats['average_case_duration'] / (cases_stats['cases_total'] - cases_stats['open_cases'])
cases = {
'cases': cases_list,
'stats': cases_stats
}
return response_success(data=cases)
@manage_customers_blueprint.route('/manage/customers/update/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.customers_read, no_cid_required=True)
def view_customer_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
form = AddCustomerForm()
customer = get_client(cur_id)
if not customer:
return response_error("Invalid Customer ID")
form.customer_name.render_kw = {'value': customer.name}
form.customer_description.data = customer.description
form.customer_sla.data = customer.sla
return render_template("modal_add_customer.html", form=form, customer=customer,
attributes=customer.custom_attributes)
@manage_customers_blueprint.route('/manage/customers/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def view_customers(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request")
try:
client = update_client(cur_id, request.json)
except ElementNotFoundException:
return response_error('Invalid Customer ID')
except ValidationError as e:
return response_error("", data=e.messages)
except Exception as e:
print(traceback.format_exc())
return response_error(f'An error occurred during Customer update. {e}')
client_schema = CustomerSchema()
return response_success("Customer updated", client_schema.dump(client))
@manage_customers_blueprint.route('/manage/customers/add/modal', methods=['GET'])
@ac_requires(Permissions.customers_read, no_cid_required=True)
def add_customers_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
form = AddCustomerForm()
attributes = get_default_custom_attributes('client')
return render_template("modal_add_customer.html", form=form, customer=None, attributes=attributes)
@manage_customers_blueprint.route('/manage/customers/add', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def add_customers(caseid):
if not request.is_json:
return response_error("Invalid request")
try:
client = create_client(request.json)
except ValidationError as e:
return response_error(msg='Error adding customer', data=e.messages, status=400)
except Exception as e:
print(traceback.format_exc())
return response_error(f'An error occurred during customer addition. {e}')
track_activity(f"Added customer {client.name}", caseid=caseid, ctx_less=True)
# Return the customer
client_schema = CustomerSchema()
return response_success("Added successfully", data=client_schema.dump(client))
@manage_customers_blueprint.route('/manage/customers/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def delete_customers(cur_id, caseid):
try:
delete_client(cur_id)
except ElementNotFoundException:
return response_error('Invalid Customer ID')
except ElementInUseException:
return response_error('Cannot delete a referenced customer')
except Exception:
return response_error('An error occurred during customer deletion')
track_activity("Deleted Customer with ID {asset_id}".format(asset_id=cur_id), caseid=caseid, ctx_less=True)
return response_success("Deleted successfully")
@manage_customers_blueprint.route('/manage/customers/<int:cur_id>/contacts/<int:contact_id>/delete', methods=['POST'])
@ac_api_requires(Permissions.customers_write, no_cid_required=True)
def delete_contact_route(cur_id, contact_id, caseid):
try:
delete_contact(contact_id)
except ElementNotFoundException:
return response_error('Invalid contact ID')
except ElementInUseException:
return response_error('Cannot delete a referenced contact')
except Exception:
return response_error('An error occurred during contact deletion')
track_activity("Deleted Customer with ID {contact_id}".format(contact_id=cur_id), caseid=caseid, ctx_less=True)
return response_success("Deleted successfully")

View File

@ -0,0 +1,87 @@
#!/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.
from flask import Blueprint, request
from app.datamgmt.manage.manage_case_objs import search_event_category_by_name
from app.models.models import EventCategory
from app.schema.marshables import EventCategorySchema
from app.util import api_login_required, ac_api_requires
from app.util import response_error
from app.util import response_success
manage_event_cat_blueprint = Blueprint('manage_event_cat',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_event_cat_blueprint.route('/manage/event-categories/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_event_categories(caseid):
lcat= EventCategory.query.with_entities(
EventCategory.id,
EventCategory.name
).all()
data = [row._asdict() for row in lcat]
return response_success("", data=data)
@manage_event_cat_blueprint.route('/manage/event-categories/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_event_category(cur_id, caseid):
lcat = EventCategory.query.with_entities(
EventCategory.id,
EventCategory.name
).filter(
EventCategory.id == cur_id
).first()
if not lcat:
return response_error(f"Event category ID {cur_id} not found")
data = lcat._asdict()
return response_success("", data=data)
@manage_event_cat_blueprint.route('/manage/event-categories/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_event_category(caseid):
if not request.is_json:
return response_error("Invalid request")
event_category = request.json.get('event_category')
if event_category is None:
return response_error("Invalid category. Got None")
exact_match = request.json.get('exact_match', False)
# Search for event category with a name that contains the specified search term
event_category = search_event_category_by_name(event_category, exact_match=exact_match)
if not event_category:
return response_error("No category found")
# Serialize the event category and return them in a JSON response
schema = EventCategorySchema(many=True)
return response_success("", data=schema.dump(event_category))

View File

@ -0,0 +1,383 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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 traceback
import marshmallow
from flask import Blueprint
from flask import render_template
from flask import request
from flask import url_for
from flask_login import current_user
from werkzeug.utils import redirect
from app import db, app
from app.datamgmt.manage.manage_cases_db import list_cases_dict
from app.datamgmt.manage.manage_groups_db import add_all_cases_access_to_group
from app.datamgmt.manage.manage_groups_db import add_case_access_to_group
from app.datamgmt.manage.manage_groups_db import delete_group
from app.datamgmt.manage.manage_groups_db import get_group
from app.datamgmt.manage.manage_groups_db import get_group_details
from app.datamgmt.manage.manage_groups_db import get_group_with_members
from app.datamgmt.manage.manage_groups_db import get_groups_list_hr_perms
from app.datamgmt.manage.manage_groups_db import remove_cases_access_from_group
from app.datamgmt.manage.manage_groups_db import remove_user_from_group
from app.datamgmt.manage.manage_groups_db import update_group_members
from app.datamgmt.manage.manage_users_db import get_user
from app.datamgmt.manage.manage_users_db import get_users_list_restricted
from app.forms import AddGroupForm
from app.iris_engine.access_control.utils import ac_get_all_access_level, ac_ldp_group_removal, ac_flag_match_mask, \
ac_ldp_group_update
from app.iris_engine.access_control.utils import ac_get_all_permissions
from app.iris_engine.access_control.utils import ac_recompute_effective_ac_from_users_list
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import AuthorizationGroupSchema
from app.util import ac_api_requires
from app.util import ac_api_return_access_denied
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
from app.iris_engine.demo_builder import protect_demo_mode_group
manage_groups_blueprint = Blueprint(
'manage_groups',
__name__,
template_folder='templates/access_control'
)
log = app.logger
@manage_groups_blueprint.route('/manage/groups/list', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_index(caseid):
groups = get_groups_list_hr_perms()
return response_success('', data=groups)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_view_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_groups.manage_groups_index', cid=caseid))
form = AddGroupForm()
group = get_group_details(cur_id)
if not group:
return response_error("Invalid group ID")
all_perms = ac_get_all_permissions()
form.group_name.render_kw = {'value': group.group_name}
form.group_description.render_kw = {'value': group.group_description}
return render_template("modal_add_group.html", form=form, group=group, all_perms=all_perms)
@manage_groups_blueprint.route('/manage/groups/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_add_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_groups.manage_groups_index', cid=caseid))
form = AddGroupForm()
all_perms = ac_get_all_permissions()
return render_template("modal_add_group.html", form=form, group=None, all_perms=all_perms)
@manage_groups_blueprint.route('/manage/groups/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_add(caseid):
if not request.is_json:
return response_error("Invalid request, expecting JSON")
data = request.get_json()
if not data:
return response_error("Invalid request, expecting JSON")
ags = AuthorizationGroupSchema()
try:
ags_c = ags.load(data)
ags.verify_unique(data)
db.session.add(ags_c)
db.session.commit()
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
track_activity(message=f"added group {ags_c.group_name}", caseid=caseid, ctx_less=True)
return response_success('', data=ags.dump(ags_c))
@manage_groups_blueprint.route('/manage/groups/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_update(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request, expecting JSON")
data = request.get_json()
if not data:
return response_error("Invalid request, expecting JSON")
group = get_group(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
ags = AuthorizationGroupSchema()
try:
data['group_id'] = cur_id
ags_c = ags.load(data, instance=group, partial=True)
if not ac_flag_match_mask(data['group_permissions'],
Permissions.server_administrator.value):
if ac_ldp_group_update(current_user.id):
db.session.rollback()
return response_error(msg="That might not be a good idea Dave",
data="Update the group permissions will lock you out")
db.session.commit()
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_success('', data=ags.dump(ags_c))
@manage_groups_blueprint.route('/manage/groups/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_delete(cur_id, caseid):
group = get_group(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
if ac_ldp_group_removal(current_user.id, group_id=group.group_id):
return response_error("I can't let you do that Dave", data="Removing this group will lock you out")
delete_group(group)
return response_success('Group deleted')
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_view(cur_id, caseid):
group = get_group_details(cur_id)
if not group:
return response_error("Invalid group ID")
ags = AuthorizationGroupSchema()
return response_success('', data=ags.dump(group))
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/members/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_members_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_groups_blueprint.manage_groups_index', cid=caseid))
group = get_group_with_members(cur_id)
if not group:
return response_error("Invalid group ID")
users = get_users_list_restricted()
return render_template("modal_add_group_members.html", group=group, users=users)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/members/update', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_members_update(cur_id, caseid):
group = get_group_with_members(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
if not request.is_json:
return response_error("Invalid request, expecting JSON")
data = request.get_json()
if not data:
return response_error("Invalid request, expecting JSON")
if not isinstance(data.get('group_members'), list):
return response_error("Expecting a list of IDs")
update_group_members(group, data.get('group_members'))
group = get_group_with_members(cur_id)
return response_success('', data=group)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/members/delete/<int:cur_id_2>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_members_delete(cur_id, cur_id_2, caseid):
group = get_group_with_members(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
user = get_user(cur_id_2)
if not user:
return response_error("Invalid user ID")
if ac_ldp_group_removal(user_id=user.id, group_id=group.group_id):
return response_error('I cannot let you do that Dave', data="Removing you from the group will make you "
"loose your access rights")
remove_user_from_group(group, user)
group = get_group_with_members(cur_id)
return response_success('Member deleted from group', data=group)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/cases-access/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_cac_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_groups.manage_groups_index', cid=caseid))
group = get_group_details(cur_id)
if not group:
return response_error("Invalid group ID")
cases_list = list_cases_dict(current_user.id)
group_cases_access = [case.get('case_id') for case in group.group_cases_access]
outer_cases_list = []
for case in cases_list:
if case.get('case_id') not in group_cases_access:
outer_cases_list.append({
"case_id": case.get('case_id'),
"case_name": case.get('case_name')
})
access_levels = ac_get_all_access_level()
return render_template("modal_add_group_cac.html", group=group, outer_cases=outer_cases_list,
access_levels=access_levels)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/cases-access/update', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_cac_add_case(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request, expecting JSON")
data = request.get_json()
if not data:
return response_error("Invalid request, expecting JSON")
group = get_group_with_members(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
if not isinstance(data.get('access_level'), int):
try:
data['access_level'] = int(data.get('access_level'))
except:
return response_error("Expecting access_level as int")
if not isinstance(data.get('cases_list'), list) and data.get('auto_follow_cases') is False:
return response_error("Expecting cases_list as list")
if data.get('auto_follow_cases') is True:
group, logs = add_all_cases_access_to_group(group, data.get('access_level'))
group.group_auto_follow = True
group.group_auto_follow_access_level = data.get('access_level')
db.session.commit()
else:
group, logs = add_case_access_to_group(group, data.get('cases_list'), data.get('access_level'))
group.group_auto_follow = False
db.session.commit()
if not group:
return response_error(msg=logs)
group = get_group_details(cur_id)
ac_recompute_effective_ac_from_users_list(group.group_members)
return response_success(data=group)
@manage_groups_blueprint.route('/manage/groups/<int:cur_id>/cases-access/delete', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_groups_cac_delete_case(cur_id, caseid):
group = get_group_with_members(cur_id)
if not group:
return response_error("Invalid group ID")
if protect_demo_mode_group(group):
return ac_api_return_access_denied(caseid=caseid)
if not request.is_json:
return response_error("Invalid request")
data = request.get_json()
if not data:
return response_error("Invalid request")
if not isinstance(data.get('cases'), list):
return response_error("Expecting cases as list")
try:
success, logs = remove_cases_access_from_group(group.group_id, data.get('cases'))
db.session.commit()
except Exception as e:
log.error("Error while removing cases access from group: {}".format(e))
log.error(traceback.format_exc())
return response_error(msg=str(e))
if success:
ac_recompute_effective_ac_from_users_list(group.group_members)
return response_success(msg="Cases access removed from group")
return response_error(msg=logs)

View File

@ -0,0 +1,199 @@
#!/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.
import marshmallow
from flask import Blueprint
from flask import render_template
from flask import request
from flask import url_for
from werkzeug.utils import redirect
from app import db
from app.datamgmt.case.case_iocs_db import get_ioc_types_list
from app.datamgmt.manage.manage_case_objs import search_ioc_type_by_name
from app.forms import AddIocTypeForm
from app.iris_engine.utils.tracker import track_activity
from app.models import Ioc
from app.models import IocType
from app.models.authorization import Permissions
from app.schema.marshables import IocTypeSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_ioc_type_blueprint = Blueprint('manage_ioc_types',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_ioc_type_blueprint.route('/manage/ioc-types/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_ioc_types(caseid):
lstatus = get_ioc_types_list()
return response_success("", data=lstatus)
@manage_ioc_type_blueprint.route('/manage/ioc-types/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_ioc_type(cur_id, caseid):
ioc_type = IocType.query.filter(IocType.type_id == cur_id).first()
if not ioc_type:
return response_error("Invalid ioc type ID {type_id}".format(type_id=cur_id))
return response_success("", data=ioc_type)
@manage_ioc_type_blueprint.route('/manage/ioc-types/update/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def view_ioc_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_ioc_types.view_ioc_modal', cid=caseid))
form = AddIocTypeForm()
ioct = IocType.query.filter(IocType.type_id == cur_id).first()
if not ioct:
return response_error("Invalid asset type ID")
form.type_name.render_kw = {'value': ioct.type_name}
form.type_description.render_kw = {'value': ioct.type_description}
form.type_taxonomy.data = ioct.type_taxonomy
form.type_validation_regex.data = ioct.type_validation_regex
form.type_validation_expect.data = ioct.type_validation_expect
return render_template("modal_add_ioc_type.html", form=form, ioc_type=ioct)
@manage_ioc_type_blueprint.route('/manage/ioc-types/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_ioc_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_ioc_types.view_ioc_modal', cid=caseid))
form = AddIocTypeForm()
return render_template("modal_add_ioc_type.html", form=form, ioc_type=None)
@manage_ioc_type_blueprint.route('/manage/ioc-types/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_ioc_type_api(caseid):
if not request.is_json:
return response_error("Invalid request")
ioct_schema = IocTypeSchema()
try:
ioct_sc = ioct_schema.load(request.get_json())
db.session.add(ioct_sc)
db.session.commit()
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
track_activity("Added ioc type {ioc_type_name}".format(ioc_type_name=ioct_sc.type_name), caseid=caseid, ctx_less=True)
# Return the assets
return response_success("Added successfully", data=ioct_sc)
@manage_ioc_type_blueprint.route('/manage/ioc-types/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def remove_ioc_type(cur_id, caseid):
type_id = IocType.query.filter(
IocType.type_id == cur_id
).first()
is_referenced = Ioc.query.filter(Ioc.ioc_type_id == cur_id).first()
if is_referenced:
return response_error("Cannot delete a referenced ioc type. Please delete any ioc of this type first.")
if type_id:
db.session.delete(type_id)
track_activity("Deleted ioc type ID {type_id}".format(type_id=cur_id), caseid=caseid, ctx_less=True)
return response_success("Deleted ioc type ID {type_id}".format(type_id=cur_id))
track_activity("Attempted to delete ioc type ID {type_id}, but was not found".format(type_id=cur_id),
caseid=caseid, ctx_less=True)
return response_error("Attempted to delete ioc type ID {type_id}, but was not found".format(type_id=cur_id))
@manage_ioc_type_blueprint.route('/manage/ioc-types/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_ioc(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request")
ioc_type = IocType.query.filter(IocType.type_id == cur_id).first()
if not ioc_type:
return response_error("Invalid ioc type ID {type_id}".format(type_id=cur_id))
ioct_schema = IocTypeSchema()
try:
ioct_sc = ioct_schema.load(request.get_json(), instance=ioc_type)
if ioct_sc:
track_activity("updated ioc type type {}".format(ioct_sc.type_name), caseid=caseid)
return response_success("IOC type updated", ioct_sc)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
return response_error("Unexpected error server-side. Nothing updated", data=ioc_type)
@manage_ioc_type_blueprint.route('/manage/ioc-types/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_ioc_type(caseid):
"""Searches for IOC types in the database.
This function searches for IOC types in the database with a name that contains the specified search term.
It returns a JSON response containing the matching IOC types.
Args:
caseid: The ID of the case associated with the request.
Returns:
A JSON response containing the matching IOC types.
"""
if not request.is_json:
return response_error("Invalid request")
ioc_type = request.json.get('ioc_type')
if ioc_type is None:
return response_error("Invalid ioc type. Got None")
exact_match = request.json.get('exact_match', False)
# Search for IOC types with a name that contains the specified search term
ioc_type = search_ioc_type_by_name(ioc_type, exact_match=exact_match)
if not ioc_type:
return response_error("No ioc types found")
# Serialize the IOC types and return them in a JSON response
ioct_schema = IocTypeSchema(many=True)
return response_success("", data=ioct_schema.dump(ioc_type))

View File

@ -0,0 +1,321 @@
#!/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 ------------------------------------------------
import json
import logging as log
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_wtf import FlaskForm
from app import app
from app.datamgmt.iris_engine.modules_db import delete_module_from_id, parse_module_parameter
from app.datamgmt.iris_engine.modules_db import get_module_config_from_id
from app.datamgmt.iris_engine.modules_db import get_module_from_id
from app.datamgmt.iris_engine.modules_db import iris_module_disable_by_id
from app.datamgmt.iris_engine.modules_db import iris_module_enable_by_id
from app.datamgmt.iris_engine.modules_db import iris_module_name_from_id
from app.datamgmt.iris_engine.modules_db import iris_module_save_parameter
from app.datamgmt.iris_engine.modules_db import iris_modules_list
from app.datamgmt.iris_engine.modules_db import is_mod_configured
from app.datamgmt.iris_engine.modules_db import module_list_hooks_view
from app.forms import AddModuleForm
from app.forms import UpdateModuleParameterForm
from app.iris_engine.module_handler.module_handler import check_module_health
from app.iris_engine.module_handler.module_handler import instantiate_module_from_name
from app.iris_engine.module_handler.module_handler import iris_update_hooks
from app.iris_engine.module_handler.module_handler import register_module
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
from app.schema.marshables import IrisModuleSchema
manage_modules_blueprint = Blueprint(
'manage_module',
__name__,
template_folder='templates'
)
def has_no_empty_params(rule):
defaults = rule.defaults if rule.defaults is not None else ()
arguments = rule.arguments if rule.arguments is not None else ()
return len(defaults) >= len(arguments)
# CONTENT ------------------------------------------------
@manage_modules_blueprint.route("/sitemap")
@ac_requires()
def site_map(caseid, url_redir):
links = []
for rule in app.url_map.iter_rules():
# Filter out rules we can't navigate to in a browser
# and rules that require parameters
if "GET" in rule.methods and has_no_empty_params(rule):
url = url_for(rule.endpoint, **(rule.defaults or {}))
links.append((url, rule.endpoint))
return response_success('', data=links)
@manage_modules_blueprint.route('/manage/modules', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_modules_index(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_module.manage_modules_index', cid=caseid))
form = FlaskForm()
return render_template("manage_modules.html", form=form)
@manage_modules_blueprint.route('/manage/modules/list', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_modules_list(caseid):
output = iris_modules_list()
return response_success('', data=output)
@manage_modules_blueprint.route('/manage/modules/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_module(caseid):
if request.json is None:
return response_error('Invalid request')
module_name = request.json.get('module_name')
# Try to import the module
try:
# Try to instantiate the module
log.info(f'Trying to add module {module_name}')
class_, logs = instantiate_module_from_name(module_name)
if not class_:
return response_error(f"Cannot import module. {logs}")
# Check the health of the module
is_ready, logs = check_module_health(class_)
if not is_ready:
return response_error("Cannot import module. Health check didn't pass. Please check logs below", data=logs)
# Registers into Iris DB for further calls
module, message = register_module(module_name)
if module is None:
track_activity(f"addition of IRIS module {module_name} was attempted and failed",
caseid=caseid, ctx_less=True)
return response_error(f'Unable to register module: {message}')
track_activity(f"IRIS module {module_name} was added", caseid=caseid, ctx_less=True)
module_schema = IrisModuleSchema()
return response_success(message, data=module_schema.dump(module))
except Exception as e:
traceback.print_exc()
return response_error(e.__str__())
@manage_modules_blueprint.route('/manage/modules/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_module_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_modules.add_module', cid=caseid))
module = None
form = AddModuleForm()
return render_template("modal_add_module.html", form=form, module=module)
@manage_modules_blueprint.route('/manage/modules/get-parameter/<param_name>', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def getmodule_param(param_name, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_modules.add_module', cid=caseid))
form = UpdateModuleParameterForm()
mod_config, mod_id, mod_name, _, parameter = parse_module_parameter(param_name)
if mod_config is None:
return response_error('Invalid parameter')
return render_template("modal_update_parameter.html", parameter=parameter, mod_name=mod_name, mod_id=mod_id,
form=form)
@manage_modules_blueprint.route('/manage/modules/set-parameter/<param_name>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_module_param(param_name, caseid):
if request.json is None:
return response_error('Invalid request')
mod_config, mod_id, mod_name, mod_iname, parameter = parse_module_parameter(param_name)
if mod_config is None:
return response_error('Invalid parameter')
parameter_value = request.json.get('parameter_value')
if iris_module_save_parameter(mod_id, mod_config, parameter['param_name'], parameter_value):
track_activity(f"parameter {parameter['param_name']} of mod ({mod_name}) #{mod_id} was updated",
caseid=caseid, ctx_less=True)
success, logs = iris_update_hooks(mod_iname, mod_id)
if not success:
return response_error("Unable to update hooks", data=logs)
return response_success("Saved", logs)
return response_error('Malformed request', status=400)
@manage_modules_blueprint.route('/manage/modules/update/<int:mod_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def view_module(mod_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_modules.view_module', cid=caseid, mod_id=mod_id))
form = AddModuleForm()
if mod_id:
module = get_module_from_id(mod_id)
config = module.module_config
is_configured, missing_params = is_mod_configured(config)
return render_template("modal_module_info.html", form=form, data=module,
config=config, is_configured=is_configured, missing_params=missing_params)
return response_error('Malformed request', status=400)
@manage_modules_blueprint.route('/manage/modules/enable/<int:mod_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def enable_module(mod_id, caseid):
module_name = iris_module_name_from_id(mod_id)
if module_name is None:
return response_error('Invalid module ID', status=400)
if not iris_module_enable_by_id(mod_id):
return response_error('Unable to enable module')
success, logs = iris_update_hooks(module_name, mod_id)
if not success:
return response_error("Unable to update hooks when enabling module", data=logs)
track_activity(f"IRIS module ({module_name}) #{mod_id} enabled",
caseid=caseid, ctx_less=True)
return response_success('Module enabled', data=logs)
@manage_modules_blueprint.route('/manage/modules/disable/<int:module_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def disable_module(module_id, caseid):
if iris_module_disable_by_id(module_id):
track_activity(f"IRIS module #{module_id} disabled",
caseid=caseid, ctx_less=True)
return response_success('Module disabled')
return response_error('Unable to disable module')
@manage_modules_blueprint.route('/manage/modules/remove/<int:module_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def view_delete_module(module_id, caseid):
try:
delete_module_from_id(module_id=module_id)
track_activity(f"IRIS module #{module_id} deleted",
caseid=caseid, ctx_less=True)
return response_success("Deleted")
except Exception as e:
log.error(e.__str__())
return response_error(f"Cannot delete module. Error {e.__str__()}")
@manage_modules_blueprint.route('/manage/modules/export-config/<int:module_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def export_mod_config(module_id, caseid):
mod_config, mod_name, _ = get_module_config_from_id(module_id)
if mod_name:
data = {
"module_name": mod_name,
"module_configuration": mod_config
}
return response_success(data=data)
return response_error(f"Module ID {module_id} not found")
@manage_modules_blueprint.route('/manage/modules/import-config/<int:module_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def import_mod_config(module_id, caseid):
mod_config, mod_name, _ = get_module_config_from_id(module_id)
logs = []
parameters_data = request.get_json().get('module_configuration')
if type(parameters_data) is not list:
try:
parameters = json.loads(parameters_data)
except Exception as e:
return response_error('Invalid data', data="Not a JSON file")
else:
parameters = parameters_data
for param in parameters:
param_name = param.get('param_name')
parameter_value = param.get('value')
if not iris_module_save_parameter(module_id, mod_config, param_name, parameter_value):
logs.append(f'Unable to save parameter {param_name}')
track_activity(f"parameters of mod #{module_id} were updated from config file",
caseid=caseid, ctx_less=True)
if len(logs) == 0:
msg = "Successfully imported data."
else:
msg = "Configuration is partially imported, we got errors with the followings:\n- " + "\n- ".join(logs)
return response_success(msg)
@manage_modules_blueprint.route('/manage/modules/hooks/list', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def view_modules_hook(caseid):
output = module_list_hooks_view()
data = [item._asdict() for item in output]
return response_success('', data=data)

View File

@ -0,0 +1,47 @@
#!/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 ------------------------------------------------
from flask import Blueprint
from flask import redirect
from flask import render_template
from flask import url_for
from app.forms import AddAssetForm
from app.models.authorization import Permissions
from app.util import ac_requires
manage_objects_blueprint = Blueprint('manage_objects',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_objects_blueprint.route('/manage/objects')
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_objects(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_objects.manage_objects', cid=caseid))
form = AddAssetForm()
# Return default page of case management
return render_template('manage_objects.html', form=form)

View File

@ -0,0 +1,86 @@
#!/usr/bin/env python3
#
# IRIS Source Code
# 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.
from flask import Blueprint, Response, request
from app.datamgmt.manage.manage_common import get_severity_by_id, get_severities_list, search_severity_by_name
from app.schema.marshables import SeveritySchema
from app.util import ac_api_requires, response_error
from app.util import response_success
manage_severities_blueprint = Blueprint('manage_severities',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_severities_blueprint.route('/manage/severities/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_severities(caseid: int) -> Response:
"""
Get the list of severities
Args:
caseid (int): case id
Returns:
Flask Response object
"""
l_cl = get_severities_list()
schema = SeveritySchema()
return response_success("", data=schema.dump(l_cl, many=True))
@manage_severities_blueprint.route('/manage/severities/<int:severity_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_case_alert_status(severity_id: int, caseid: int) -> Response:
"""
Get the alert status
Args:
severity_id (int): severity id
caseid (int): case id
"""
cl = get_severity_by_id(severity_id)
schema = SeveritySchema()
return response_success("", data=schema.dump(cl))
@manage_severities_blueprint.route('/manage/severities/search', methods=['POST'])
@ac_api_requires(no_cid_required=True)
def search_analysis_status(caseid):
if not request.is_json:
return response_error("Invalid request")
severity_name = request.json.get('severity_name')
if severity_name is None:
return response_error("Invalid severity. Got None")
exact_match = request.json.get('exact_match', False)
# Search for severity with a name that contains the specified search term
severity = search_severity_by_name(severity_name, exact_match=exact_match)
if not severity:
return response_error("No severity found")
# Serialize the severity and return them in a JSON response
schema = SeveritySchema(many=True)
return response_success("", data=schema.dump(severity))

View File

@ -0,0 +1,141 @@
#!/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 ------------------------------------------------
import marshmallow
from flask import Blueprint
from flask import render_template
from flask import request
from flask import url_for
from flask_wtf import FlaskForm
from werkzeug.utils import redirect
from app import app
from app import celery
from app import db
from app.datamgmt.manage.manage_srv_settings_db import get_alembic_revision
from app.datamgmt.manage.manage_srv_settings_db import get_srv_settings
from app.iris_engine.backup.backup import backup_iris_db
from app.iris_engine.updater.updater import inner_init_server_update
from app.iris_engine.updater.updater import is_updates_available
from app.iris_engine.updater.updater import remove_periodic_update_checks
from app.iris_engine.updater.updater import setup_periodic_update_checks
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import ServerSettingsSchema
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_srv_settings_blueprint = Blueprint(
'manage_srv_settings_blueprint',
__name__,
template_folder='templates'
)
@manage_srv_settings_blueprint.route('/manage/server/make-update', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_update(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_srv_settings_blueprint.manage_settings', cid=caseid))
# Return default page of case management
return render_template('manage_make_update.html')
@manage_srv_settings_blueprint.route('/manage/server/backups/make-db', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_make_db_backup(caseid):
has_error, logs = backup_iris_db()
if has_error:
rep = response_error('Backup failed', data=logs)
else:
rep = response_success('Backup done', data=logs)
return rep
@manage_srv_settings_blueprint.route('/manage/server/check-updates/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_check_updates_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_srv_settings_blueprint.manage_settings', cid=caseid))
has_updates, updates_content, _ = is_updates_available()
# Return default page of case management
return render_template('modal_server_updates.html', has_updates=has_updates, updates_content=updates_content)
@manage_srv_settings_blueprint.route('/manage/settings', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_settings(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_srv_settings_blueprint.manage_settings', cid=caseid))
form = FlaskForm()
server_settings = get_srv_settings()
versions = {
"iris_current": app.config.get('IRIS_VERSION'),
"api_min": app.config.get('API_MIN_VERSION'),
"api_current": app.config.get('API_MAX_VERSION'),
"interface_min": app.config.get('MODULES_INTERFACE_MIN_VERSION'),
"interface_current": app.config.get('MODULES_INTERFACE_MAX_VERSION'),
"db_revision": get_alembic_revision()
}
# Return default page of case management
return render_template('manage_srv_settings.html', form=form, settings=server_settings, versions=versions)
@manage_srv_settings_blueprint.route('/manage/settings/update', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_update_settings(caseid):
if not request.is_json:
return response_error('Invalid request')
srv_settings_schema = ServerSettingsSchema()
server_settings = get_srv_settings()
original_update_check = server_settings.enable_updates_check
try:
srv_settings_sc = srv_settings_schema.load(request.get_json(), instance=server_settings)
db.session.commit()
if original_update_check != srv_settings_sc.enable_updates_check:
if srv_settings_sc.enable_updates_check:
setup_periodic_update_checks(celery)
else:
remove_periodic_update_checks()
if srv_settings_sc:
track_activity("Server settings updated", caseid=caseid)
return response_success("Server settings updated", srv_settings_sc)
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)

View File

@ -0,0 +1,65 @@
#!/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.
from flask import Blueprint
from app.models.models import TaskStatus
from app.util import api_login_required, ac_api_requires
from app.util import response_error
from app.util import response_success
manage_task_status_blueprint = Blueprint('manage_task_status',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_task_status_blueprint.route('/manage/task-status/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_task_status(caseid):
lstatus = TaskStatus.query.with_entities(
TaskStatus.id,
TaskStatus.status_name,
TaskStatus.status_bscolor,
TaskStatus.status_description
).all()
data = [row._asdict() for row in lstatus]
return response_success("", data=data)
# CONTENT ------------------------------------------------
@manage_task_status_blueprint.route('/manage/task-status/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def view_task_status(cur_id, caseid):
lstatus = TaskStatus.query.with_entities(
TaskStatus.id,
TaskStatus.status_name,
TaskStatus.status_bscolor,
TaskStatus.status_description
).filter(
TaskStatus.id == cur_id
).first()
if not lstatus:
return response_error(f"Task status ID #{cur_id} not found")
return response_success(data=lstatus._asdict())

View File

@ -0,0 +1,213 @@
#!/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 ------------------------------------------------
import os
import random
import string
from datetime import datetime
from flask import Blueprint
from flask import flash
from flask import redirect
from flask import render_template
from flask import request
from flask import send_file
from flask import url_for
from flask_login import current_user
from app import app
from app import db
from app.forms import AddReportTemplateForm
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.models.authorization import User
from app.models.models import CaseTemplateReport
from app.models.models import Languages
from app.models.models import ReportType
from app.util import ac_api_requires
from app.util import ac_requires
from app.util import response_error
from app.util import response_success
manage_templates_blueprint = Blueprint(
'manage_templates',
__name__,
template_folder='templates'
)
ALLOWED_EXTENSIONS = {'md', 'html', 'doc', 'docx'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def get_random_string(length):
letters = string.ascii_lowercase
result_str = ''.join(random.choice(letters) for i in range(length))
return result_str
# CONTENT ------------------------------------------------
@manage_templates_blueprint.route('/manage/templates')
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_report_templates(caseid, url_redir):
if url_redir:
redirect(url_for('manage_templates.manage_report_templates', cid=caseid))
form = AddReportTemplateForm()
form.report_language.choices = [(c.id, c.name.capitalize()) for c in Languages.query.all()]
# Return default page of case management
return render_template('manage_templates.html', form=form)
@manage_templates_blueprint.route('/manage/templates/list')
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def report_templates_list(caseid):
# Get all templates
templates = CaseTemplateReport.query.with_entities(
CaseTemplateReport.name,
CaseTemplateReport.description,
CaseTemplateReport.naming_format,
CaseTemplateReport.date_created,
User.name.label('created_by'),
Languages.code,
ReportType.name.label('type_name'),
CaseTemplateReport.id
).join(
CaseTemplateReport.created_by_user,
CaseTemplateReport.language,
CaseTemplateReport.report_type
).all()
data = [row._asdict() for row in templates]
# Return the assets
return response_success("", data=data)
@manage_templates_blueprint.route('/manage/templates/add/modal', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_template_modal(caseid):
report_template = CaseTemplateReport()
form = AddReportTemplateForm()
form.report_language.choices = [(c.id, c.name.capitalize()) for c in Languages.query.all()]
form.report_type.choices = [(c.id, c.name) for c in ReportType.query.all()]
return render_template("modal_add_report_template.html", form=form, report_template=report_template)
@manage_templates_blueprint.route('/manage/templates/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_template(caseid):
report_template = CaseTemplateReport()
report_template.name = request.form.get('report_name', '', type=str)
report_template.description = request.form.get('report_description', '', type=str)
report_template.naming_format = request.form.get('report_name_format', '', type=str)
report_template.language_id = request.form.get('report_language', '', type=int)
report_template.report_type_id = request.form.get('report_type', '', type=int)
report_template.created_by_user_id = current_user.id
report_template.date_created = datetime.utcnow()
template_file = request.files['file']
if template_file.filename == '':
flash('No selected file')
return redirect(request.url)
if template_file and allowed_file(template_file.filename):
_, extension = os.path.splitext(template_file.filename)
filename = get_random_string(18) + extension
try:
template_file.save(os.path.join(app.config['TEMPLATES_PATH'], filename))
except Exception as e:
return response_error(f"Unable to add template. {e}")
report_template.internal_reference = filename
db.session.add(report_template)
db.session.commit()
track_activity(f"report template '{report_template.name}' added", caseid=caseid, ctx_less=True)
ret = {
"report_id": report_template.id,
"report_name": report_template.name,
"report_description": report_template.description,
"report_language_id": report_template.language_id,
"report_name_format": report_template.naming_format,
"report_type_id": report_template.report_type_id
}
return response_success("Added successfully", data=ret)
return response_error("File is invalid")
@manage_templates_blueprint.route('/manage/templates/download/<report_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def download_template(report_id, caseid):
if report_id != 0:
report_template = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first()
fpath = os.path.join(app.config['TEMPLATES_PATH'], report_template.internal_reference)
_, extension = os.path.splitext(report_template.internal_reference)
resp = send_file(fpath, as_attachment=True, download_name=f"{report_template.name}.{extension}")
return resp
else:
return response_error("Unable to download file")
@manage_templates_blueprint.route('/manage/templates/delete/<report_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def delete_template(report_id, caseid):
error = None
report_template = CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).first()
if report_template is None:
return response_error('Template not found')
report_name = report_template.name
try:
os.unlink(os.path.join(app.config['TEMPLATES_PATH'], report_template.internal_reference))
except Exception as e:
error = f"Template reference will be deleted but there has been some errors. {e}"
finally:
CaseTemplateReport.query.filter(CaseTemplateReport.id == report_id).delete()
db.session.commit()
if error:
return response_error(error)
track_activity(f"report template '{report_name}' deleted", caseid=caseid, ctx_less=True)
return response_success("Deleted successfully", data=error)

View File

@ -0,0 +1,51 @@
#!/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.
from flask import Blueprint
from app.models import Tlp
from app.util import api_login_required, ac_api_requires
from app.util import response_error
from app.util import response_success
manage_tlp_type_blueprint = Blueprint('manage_tlp_types',
__name__,
template_folder='templates')
# CONTENT ------------------------------------------------
@manage_tlp_type_blueprint.route('/manage/tlp/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def list_tlp_types(caseid):
lstatus = Tlp.query.all()
return response_success("", data=lstatus)
@manage_tlp_type_blueprint.route('/manage/tlp/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def get_tlp_type(cur_id, caseid):
tlp_type = Tlp.query.filter(Tlp.tlp_id == cur_id).first()
if not tlp_type:
return response_error("Invalid TLP ID {type_id}".format(type_id=cur_id))
return response_success("", data=tlp_type)

View File

@ -0,0 +1,489 @@
#!/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.
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 app import app
from app import db
from app.datamgmt.manage.manage_cases_db import list_cases_dict
from app.datamgmt.manage.manage_groups_db import get_groups_list
from app.datamgmt.manage.manage_srv_settings_db import get_srv_settings
from app.datamgmt.manage.manage_users_db import add_case_access_to_user
from app.datamgmt.manage.manage_users_db import create_user
from app.datamgmt.manage.manage_users_db import delete_user
from app.datamgmt.manage.manage_users_db import get_user
from app.datamgmt.manage.manage_users_db import get_user_by_username
from app.datamgmt.manage.manage_users_db import get_user_details
from app.datamgmt.manage.manage_users_db import get_user_effective_permissions
from app.datamgmt.manage.manage_users_db import get_users_list
from app.datamgmt.manage.manage_users_db import get_users_list_restricted
from app.datamgmt.manage.manage_users_db import remove_case_access_from_user
from app.datamgmt.manage.manage_users_db import remove_cases_access_from_user
from app.datamgmt.manage.manage_users_db import update_user
from app.datamgmt.manage.manage_users_db import update_user_groups
from app.forms import AddUserForm
from app.iris_engine.access_control.utils import ac_get_all_access_level
from app.iris_engine.utils.tracker import track_activity
from app.models.authorization import Permissions
from app.schema.marshables import UserSchema
from app.util import ac_api_requires
from app.util import ac_api_return_access_denied
from app.util import ac_requires
from app.util import is_authentication_local
from app.util import response_error
from app.util import response_success
from app.iris_engine.demo_builder import protect_demo_mode_user
manage_users_blueprint = Blueprint(
'manage_users',
__name__,
template_folder='templates'
)
log = app.logger
@manage_users_blueprint.route('/manage/users/list', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_users_list(caseid):
users = get_users_list()
return response_success('', data=users)
@manage_users_blueprint.route('/manage/users/add/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def add_user_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_users.add_user', cid=caseid))
user = None
form = AddUserForm()
server_settings = get_srv_settings()
return render_template("modal_add_user.html", form=form, user=user, server_settings=server_settings)
@manage_users_blueprint.route('/manage/users/add', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def add_user(caseid):
try:
# validate before saving
user_schema = UserSchema()
jsdata = request.get_json()
jsdata['user_id'] = 0
jsdata['active'] = jsdata.get('active', True)
cuser = user_schema.load(jsdata, partial=True)
user = create_user(user_name=cuser.name,
user_login=cuser.user,
user_email=cuser.email,
user_password=cuser.password,
user_active=jsdata.get('active'),
user_is_service_account=cuser.is_service_account)
udata = user_schema.dump(user)
udata['user_api_key'] = user.api_key
del udata['user_password']
if cuser:
track_activity("created user {}".format(user.user), caseid=caseid, ctx_less=True)
return response_success("user created", data=udata)
return response_error("Unable to create user for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@manage_users_blueprint.route('/manage/users/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def view_user(cur_id, caseid):
user = get_user_details(user_id=cur_id)
if not user:
return response_error("Invalid user ID")
return response_success(data=user)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def view_user_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_users.add_user', cid=caseid))
form = AddUserForm()
user = get_user_details(cur_id, include_api_key=True)
if not user:
return response_error("Invalid user ID")
permissions = get_user_effective_permissions(cur_id)
form.user_login.render_kw = {'value': user.get('user_login')}
form.user_name.render_kw = {'value': user.get('user_name')}
form.user_email.render_kw = {'value': user.get('user_email')}
form.user_is_service_account.render_kw = {'checked': user.get('user_is_service_account')}
server_settings = get_srv_settings()
return render_template("modal_add_user.html", form=form, user=user, server_settings=server_settings,
permissions=permissions)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/groups/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_group_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_users.add_user', cid=caseid))
user = get_user_details(cur_id)
if not user:
return response_error("Invalid user ID")
groups = get_groups_list()
return render_template("modal_manage_user_groups.html", groups=groups, user=user)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/groups/update', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_group_(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request", status=400)
if not request.json.get('groups_membership'):
return response_error("Invalid request", status=400)
if type(request.json.get('groups_membership')) is not list:
return response_error("Expected list of groups ID", status=400)
user = get_user_details(cur_id)
if not user:
return response_error("Invalid user ID")
update_user_groups(user_id=cur_id,
groups=request.json.get('groups_membership'))
track_activity(f"groups membership of user {user.get('user')} updated", caseid=caseid, ctx_less=True)
return response_success("User groups updated", data=user)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/cases-access/modal', methods=['GET'])
@ac_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_cac_modal(cur_id, caseid, url_redir):
if url_redir:
return redirect(url_for('manage_users.add_user', cid=caseid))
user = get_user_details(cur_id)
if not user:
return response_error("Invalid user ID")
cases_list = list_cases_dict(current_user.id)
user_cases_access = [case.get('case_id') for case in user.get('user_cases_access')]
outer_cases_list = []
for case in cases_list:
if case.get('case_id') not in user_cases_access:
outer_cases_list.append({
"case_id": case.get('case_id'),
"case_name": case.get('case_name')
})
access_levels = ac_get_all_access_level()
return render_template("modal_add_user_cac.html", user=user, outer_cases=outer_cases_list,
access_levels=access_levels)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/cases-access/update', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_cac_add_case(cur_id, caseid):
if not request.is_json:
return response_error("Invalid request, expecting JSON")
data = request.get_json()
if not data:
return response_error("Invalid request, expecting JSON")
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID")
if not isinstance(data.get('access_level'), int):
try:
data['access_level'] = int(data.get('access_level'))
except:
return response_error("Expecting access_level as int")
if not isinstance(data.get('cases_list'), list):
return response_error("Expecting cases_list as list")
user, logs = add_case_access_to_user(user, data.get('cases_list'), data.get('access_level'))
if not user:
return response_error(msg=logs)
track_activity(f"case access level {data.get('access_level')} for case(s) {data.get('cases_list')} "
f"set for user {user.user}", caseid=caseid, ctx_less=True)
group = get_user_details(cur_id)
return response_success(data=group)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/cases-access/delete', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_cac_delete_cases(cur_id, caseid):
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID")
if not request.is_json:
return response_error("Invalid request")
data = request.get_json()
if not data:
return response_error("Invalid request")
if not isinstance(data.get('cases'), list):
return response_error("Expecting cases as list")
try:
success, logs = remove_cases_access_from_user(user.id, data.get('cases'))
db.session.commit()
except Exception as e:
log.error("Error while removing cases access from user: {}".format(e))
log.error(traceback.format_exc())
return response_error(msg=str(e))
if success:
track_activity(f"cases access for case(s) {data.get('cases')} deleted for user {user.user}", caseid=caseid,
ctx_less=True)
user = get_user_details(cur_id)
return response_success(msg="User case access updated", data=user)
return response_error(msg=logs)
@manage_users_blueprint.route('/manage/users/<int:cur_id>/case-access/delete', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def manage_user_cac_delete_case(cur_id, caseid):
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID")
if not request.is_json:
return response_error("Invalid request")
data = request.get_json()
if not data:
return response_error("Invalid request")
if not isinstance(data.get('case'), int):
return response_error("Expecting cases as int")
try:
success, logs = remove_case_access_from_user(user.id, data.get('case'))
db.session.commit()
except Exception as e:
log.error("Error while removing cases access from user: {}".format(e))
log.error(traceback.format_exc())
return response_error(msg=str(e))
if success:
track_activity(f"case access for case {data.get('case')} deleted for user {user.user}", caseid=caseid,
ctx_less=True)
user = get_user_details(cur_id)
return response_success(msg="User case access updated", data=user)
return response_error(msg=logs)
@manage_users_blueprint.route('/manage/users/update/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def update_user_api(cur_id, caseid):
try:
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID for this case")
if protect_demo_mode_user(user):
return ac_api_return_access_denied(caseid=caseid)
# validate before saving
user_schema = UserSchema()
jsdata = request.get_json()
jsdata['user_id'] = cur_id
cuser = user_schema.load(jsdata, instance=user, partial=True)
update_user(password=jsdata.get('user_password'),
user=user)
db.session.commit()
if cuser:
track_activity("updated user {}".format(user.user), caseid=caseid, ctx_less=True)
return response_success("User updated", data=user_schema.dump(user))
return response_error("Unable to update user for internal reasons")
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.messages, status=400)
@manage_users_blueprint.route('/manage/users/deactivate/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def deactivate_user_api(cur_id, caseid):
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID for this case")
if protect_demo_mode_user(user):
return ac_api_return_access_denied(caseid=caseid)
if current_user.id == cur_id:
return response_error('We do not recommend deactivating yourself for obvious reasons')
user.active = False
db.session.commit()
user_schema = UserSchema()
track_activity(f"user {user.user} deactivated", caseid=caseid, ctx_less=True)
return response_success("User deactivated", data=user_schema.dump(user))
@manage_users_blueprint.route('/manage/users/activate/<int:cur_id>', methods=['GET'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def activate_user_api(cur_id, caseid):
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID for this case")
if protect_demo_mode_user(user):
return ac_api_return_access_denied(caseid=caseid)
user.active = True
db.session.commit()
user_schema = UserSchema()
track_activity(f"user {user.user} activated", caseid=caseid, ctx_less=True)
return response_success("User activated", data=user_schema.dump(user))
if is_authentication_local():
@manage_users_blueprint.route('/manage/users/delete/<int:cur_id>', methods=['POST'])
@ac_api_requires(Permissions.server_administrator, no_cid_required=True)
def view_delete_user(cur_id, caseid):
try:
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID")
if protect_demo_mode_user(user):
return ac_api_return_access_denied(caseid=caseid)
if user.active is True:
track_activity(message="tried to delete active user ID {}".format(cur_id), caseid=caseid, ctx_less=True)
return response_error("Cannot delete active user")
delete_user(user.id)
track_activity(message="deleted user ID {}".format(cur_id), caseid=caseid, ctx_less=True)
return response_success("Deleted user ID {}".format(cur_id))
except Exception:
db.session.rollback()
track_activity(message="tried to delete active user ID {}".format(cur_id), caseid=caseid, ctx_less=True)
return response_error("Cannot delete active user")
# Unrestricted section - non admin available
@manage_users_blueprint.route('/manage/users/lookup/id/<int:cur_id>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def exists_user_restricted(cur_id, caseid):
user = get_user(cur_id)
if not user:
return response_error("Invalid user ID")
output = {
"user_login": user.user,
"user_id": user.id,
"user_name": user.name
}
return response_success(data=output)
@manage_users_blueprint.route('/manage/users/lookup/login/<string:login>', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def lookup_name_restricted(login, caseid):
user = get_user_by_username(login)
if not user:
return response_error("Invalid login")
output = {
"user_login": user.user,
"user_id": user.id,
"user_uuid": user.uuid,
"user_name": user.name,
"user_email": user.email,
"user_active": user.active
}
return response_success(data=output)
@manage_users_blueprint.route('/manage/users/restricted/list', methods=['GET'])
@ac_api_requires(no_cid_required=True)
def manage_users_list_restricted(caseid):
users = get_users_list_restricted()
return response_success('', data=users)

View File

@ -0,0 +1,125 @@
{% extends "layouts/default.html" %}
{% block title %} Access Control {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-multiselect.min.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading collapsed" href="#collapse_user_mgmt" title="Click to unfold" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_user_mgmt">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">Users</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_users(true);">Refresh</button>
<a class="btn btn-sm btn-dark float-right ml-2" href="access-control/audit/users?cid={{ session['current_case'].case_id }}">Audit users</a>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_user();">Add user</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_user_mgmt">
<div class="table-responsive" id="users_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="users_table">
<thead>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Login Name</th>
<th>Email</th>
<th>Active</th>
<th>Service Account</th>
</tr>
</thead>
<tfoot>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Login Name</th>
<th>Email</th>
<th>Active</th>
<th>Service Account</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading collapsed" href="#collapse_groups_mgmt" title="Click to unfold" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_groups_mgmt">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">Groups</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_groups(true);">Refresh</button>
<button class="btn btn-sm btn-dark float-right" onclick="add_group();">Add group</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_groups_mgmt">
<div class="table-responsive" id="groups_table_wrapper">
<div class="selectgroup">
<span id="groups_table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="groups_table">
<thead>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Description</th>
<th>Permissions</th>
<th>#Members</th>
</tr>
</thead>
<tfoot>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Description</th>
<th>Permissions</th>
<th>#Members</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_access_control" data-backdrop="true">
</div>
<div class="modal bg-shadow-gradient" tabindex="-1" role="dialog" id="modal_ac_additional" data-backdrop="true">
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/plugin/select/bootstrap-multiselect.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.select.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.contextualActions.min.js"></script>
<script src="/static/assets/js/iris/manage.users.js"></script>
<script src="/static/assets/js/iris/manage.cases.common.js"></script>
<script src="/static/assets/js/iris/manage.groups.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,58 @@
{% extends "layouts/default.html" %}
{% block title %} Access Control {% endblock title %}
{% block stylesheets %}
<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 %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<a class="mb-2 ml-1 text-dark" href="/manage/access-control?cid={{ session['current_case'].case_id }}"><i class="fa-solid fa-arrow-left"></i> Back</a>
<div class="card mt-2">
<div class="card-header">
<div class="row">
<div class="col col-heading">
<div class="card-title">Users audit</div>
</div>
</div>
</div>
<div class="card-body" id="">
<div class="row">
<div class="col-12">
<div class="input-group mb-3">
<select id="users_audit_select" name="users_audit_select" class="form-control ml-12"
tabindex="-1" style="width: 50%"></select>
<div class="input-group-append">
<button type="button" class="btn btn-outline-success" id="get_user_audit_btn"
onclick="get_user_audit_page();">Audit</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
</div>
</div>
</div>
</div>
<div id="user_audit_content">
</div>
</div>
</div>
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/plugin/datatables/dataTables.select.min.js"></script>
<script src="/static/assets/js/iris/manage.audit.users.js"></script>
<script src="/static/assets/js/iris/datatablesUtils.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,292 @@
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_access_control_content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">{% if not group.group_id %}Add Group {% else %} Edit {{ group.group_name }} {% endif %}</h4>
<div class="row text-center mr-4">
{% if group.group_id %}
<ul class="nav nav-pills nav-default mr-4" role="tablist">
<li class="nav-item">
<a class="nav-link active show" data-toggle="pill" href="#group_details_tab" role="tab" aria-controls="group_details_tab" aria-selected="false">Info</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#group_members_tab" role="tab" aria-controls="group_members_tab" aria-selected="false">Members</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#group_cac_tab" role="tab" aria-controls="group_cac_tab" aria-selected="false">Cases access</a>
</li>
</ul>
{% endif %}
</div>
<div class="row text-right">
<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 class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="group_details_tab">
<div class="container col-md-12" >
<form method="post" action="" id="form_new_group">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
{% if not group.group_id %}
<p class="ml-3"><i class="fa-solid fa-circle-info mr-2"></i>Members can be added once the group is created.</p>
{% endif %}
<div class="form-group">
<label for="group_name" class="mr-4">Group name *
</label>
{{ form.group_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="group_description" class="placeholder">Description *</label>
{{ form.group_description(class='form-control', autocomplete="off") }}
</div>
<div class="form-group" data-select2-id="7">
<label>Permissions *</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="group_permissions" name="group_permissions" class="form-control select2-hidden-accessible ml-12" multiple="" data-select2-id="group_permissions" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
{% if group.group_id %}
<button type="button" class="btn btn-danger mt-5"
onclick="delete_group('{{ group.group_id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_group">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_group">Save</button>
{% endif %}
</form>
</div>
</div>
<div class="tab-pane" id="group_members_tab">
<div class="container col-md-12" >
<div class="row d-flex">
<div class="col pull-right">
<button class="btn btn-dark btn-sm pull-right" onclick="refresh_group_members({{ group.group_id }});">
<span class="menu-title">Refresh</span>
</button>
<button class="btn btn-dark btn-sm pull-right mr-2" onclick="add_members_to_group({{ group.group_id }});">
<span class="menu-title">Manage</span>
</button>
</div>
</div>
<div class="row">
<table class="table display table-bordered table-striped table-hover responsive" width="100%" cellspacing="0" id="group_members_table" >
<thead>
<tr>
<th>User ID</th>
<th>User login</th>
<th>User display name</th>
</tr>
</thead>
<tfoot>
<tr>
<th>User ID</th>
<th>User login</th>
<th>User display name</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="tab-pane" id="group_cac_tab">
<div class="container col-md-12" >
<div class="row d-flex mb-2">
<div class="col">
{% if group.group_auto_follow %}
<b><i class="fa fa-triangle-exclamation text-warning mr-2"></i>This group is set to automatically include all new cases.</b>
{% endif %}
</div>
<div class="col pull-right">
<button class="btn btn-dark btn-sm pull-right" onclick="refresh_group_cac({{ group.group_id }});">
<span class="menu-title">Refresh</span>
</button>
<button class="btn btn-dark btn-sm pull-right mr-2" onclick="manage_group_cac({{ group.group_id }});">
<span class="menu-title" id="manage_group_cac_button">Set case access</span>
</button>
</div>
</div>
<div class="row mt-2">
<table class="table display table-bordered table-striped table-hover responsive" width="100%" cellspacing="0" id="group_cac_table" >
<thead>
<tr>
<th>Case ID</th>
<th>Case Name</th>
<th>Access</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Case ID</th>
<th>Case Name</th>
<th>Access</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [
{% for e in all_perms %}
{
value: {{ e.value }},
label: "{{ e.name }}"
}
{% if not loop.last %},{% endif %}
{% endfor %}
];
$('#group_permissions').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select permissions',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_permissions').multiselect('dataprovider', data );
{% if group.group_permissions_list %}
$('#group_permissions').multiselect('select', [
{% for perm in group.group_permissions_list %} {{ perm.value }}, {% endfor %}
]);
$('#org_members').multiselect('refresh')
{% endif %}
var modal_group_table = $("#group_members_table").DataTable({
dom: 'Blfrtip',
aaData: [],
aoColumns: [
{
"data": "id",
"render": function ( data, type, row ) {
return `<i class="fa-solid fa-trash-can mr-2 text-danger" style="cursor:pointer;" title="Remove from group" href="javascript:void(0)" onclick="remove_members_from_group('{{ group.group_id }}',${data})"></i>${data}`;
},
"className": "dt-center"
},
{
"data": "user",
"className": "dt-center",
"render": function (data, type, row) {
return sanitizeHTML(data);
}
},
{
"data": "name",
"className": "dt-center",
"render": function (data, type, row) {
return sanitizeHTML(data);
}
}
],
filter: true,
info: true,
ordering: true,
processing: true
});
{% if group.group_id %}
modal_group_table.rows.add({{ group.group_members|tojson }});
modal_group_table.columns.adjust().draw();
{% endif %}
var modal_group_cac_table = $("#group_cac_table").DataTable({
dom: 'Blfrtip',
aaData: [],
aoColumns: [
{
"data": "case_id",
"render": function ( data, type, row ) {
return `<i class="fa-solid fa-trash-can mr-2 text-danger" style="cursor:pointer;" title="Remove access to case" href="javascript:void(0)" onclick="remove_case_access_from_group('{{ group.group_id }}',${data})"></i>${data}`;
},
"className": "dt-center"
},
{
"data": "case_name",
"className": "dt-center",
"render": function (data, type, row) {
return `<a target="_blank" rel="noopener" href="/case?cid=${row.case_id}">${sanitizeHTML(data)}</a>`;
}
},
{
"data": "access_level_list",
"render": function ( data, type, row ) {
ret_data = "";
for (acc in data) {
ret_data += `<span class="badge ml-2 badge-light">${data[acc].name}</span>`
}
return ret_data;
},
"className": "dt-center"
}
],
filter: true,
info: true,
ordering: true,
processing: true,
select: true
});
var actionOptionsGroup = {
classes: [],
contextMenu: {
enabled: true,
isMulti: true,
xoffset: -10,
yoffset: -10,
headerRenderer: function (rows) {
if (rows.length > 1) {
return rows.length + ' items selected';
} else {
let row = rows[0];
return 'Quick action';
}
},
},
buttonList: {
enabled: false,
},
deselectAfterAction: true,
items: [],
};
{% if group.group_id %}
actionOptionsGroup.items.push({
type: 'option',
title: 'Remove access',
multi: true,
iconClass: 'fas fa-trash',
buttonClasses: ['btn', 'btn-outline-primary'],
action: function(rows){
remove_group_cases_from_group_table({{ group.group_id }}, rows);
}
});
modal_group_cac_table.contextualActions(actionOptionsGroup);
current_group_cases_access_list = {{ group.group_cases_access|tojson }};
modal_group_cac_table.rows.add(current_group_cases_access_list);
modal_group_cac_table.columns.adjust().draw();
{% endif %}
</script>

View File

@ -0,0 +1,90 @@
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Set case access</h4>
<div class="row text-right">
<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 class="modal-body">
<div class="container col-md-12" >
<div class="row">
<div class="form-group" data-select2-id="7">
<label>Set cases access of group <i>{{ group.group_name }}</i> *</label>
<div class="row">
<div class="col-12">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="enable_auto_follow_cases" name="enable_auto_follow_cases" {% if group.group_auto_follow %}checked{% endif %}>
<span class="form-check-sign">Apply to currents and futures cases</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<select id="group_case_access_select" name="org_case_access_select" class="form-control select2-hidden-accessible ml-12" multiple="multiple"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
<select id="group_case_ac_select" name="org_case_ac_select" class="form-control select2-hidden-accessible ml-12"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="grant_case_access_to_group">Set access</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [];
$('#group_case_access_select').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select case',
emptyText: 'No case available to add',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_case_access_select').multiselect('dataprovider', [{% for ocs in outer_cases %}
{ label: "{{ ocs.case_name }}", value: {{ ocs.case_id }} }, {% endfor %}]);
$('#group_case_access_select').multiselect('refresh')
$('#group_case_ac_select').multiselect({
nonSelectedText: 'Select access level',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_case_ac_select').multiselect('dataprovider', [{% for acc in access_levels %}
{ label: "{{ acc.name }}", value: {{ acc.value }} }, {% endfor %}]);
$('#group_case_ac_select').multiselect('refresh');
$('#enable_auto_follow_cases').on('change', function() {
if (this.checked) {
$('#group_case_access_select').multiselect('disable');
} else {
$('#group_case_access_select').multiselect('enable');
}
});
if ($('#enable_auto_follow_cases').is(':checked')) {
$('#group_case_access_select').multiselect('disable');
}
</script>

View File

@ -0,0 +1,62 @@
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Manage members of group {{ group.group_name }}</h4>
<div class="row text-right">
<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 class="modal-body">
<div class="container col-md-12" >
<div class="row">
<div class="form-group" data-select2-id="7">
<label>Members *</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="group_members" name="group_members" class="form-control select2-hidden-accessible ml-12" multiple=""
data-select2-id="group_members" tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="save_group_members">Save</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [];
if (current_users_list.length === 0) {
refresh_users();
}
for (user in current_users_list) {
data.push({
label: `${current_users_list[user].user_login} (${current_users_list[user].user_name} - ${current_users_list[user].user_email})`,
value: current_users_list[user].user_id
});
}
$('#group_members').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select members',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_members').multiselect('dataprovider', data );
{% if group.group_members %}
$('#group_members').multiselect('select', [
{% for member in group.group_members %} {{ member.id }}, {% endfor %}
]);
$('#group_members').multiselect('refresh')
{% endif %}
</script>

View File

@ -0,0 +1,343 @@
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_access_control_content">
<div class="modal-header">
<div class="row w-100 d-flex justify-content-center">
<h4 class="modal-title ml-4 mt-3">{% if not user.user_id %}Add User{% else %} Edit user {% endif %}</h4>
<div class="col">
{% if user.user_id %}
<ul class="nav nav-pills nav-default justify-content-center" role="tablist">
<li class="nav-item">
<a class="nav-link active show" data-toggle="pill" href="#user_details_tab" role="tab" aria-controls="user_details_tab" aria-selected="false">Info</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#user_permissions_tab" role="tab" aria-controls="user_permissions_tab" aria-selected="false">Permissions</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#user_groups_tab" role="tab" aria-controls="user_groups_tab" aria-selected="false">Groups</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#user_cac_tab" role="tab" aria-controls="user_cac_tab" aria-selected="false">Cases access</a>
</li>
</ul>
{% endif %}
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
</div>
</div>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="user_details_tab">
<div class="container col-md-12">
<form method="post" action="" id="form_new_user">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
{% if not user.user_id %}
<p class="ml-3"><i class="fa-solid fa-circle-info mr-2"></i>Permissions and groups memberships can be set once the user is created.</p>
{% endif %}
{% if user.user_is_service_account %}
<p class="ml-3 text-warning-high"><i class="fa-solid fa-circle-info mr-2"></i>This is a service account. It cannot login interactively nor have a password.</p>
{% endif %}
<div class="form-group">
<label for="user_name" class="mr-4">Full name
</label>
{{ form.user_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="user_login" class="placeholder">Login</label>
{{ form.user_login(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="user_email" class="placeholder">Email</label>
{{ form.user_email(class='form-control', autocomplete="off") }}
</div>
{% if not user.user_is_service_account %}
<div class="form-group mt-3" id="formGroupUserPassword">
<label for="user_pwd" class="placeholder">Password (optional for service accounts)</label>
<ul>
<li><small>Must contain at least {{ server_settings.password_policy_min_length }} chars</small></li>
{% if server_settings.password_policy_upper_case %}
<li class="text-sm"><small>Must contain at least an upper case</small></li>
{% endif %}
{% if server_settings.password_policy_lower_case %}
<li class="text-sm"><small>Must contain at least a lower case</small></li>
{% endif %}
{% if server_settings.password_policy_digit %}
<li class="text-sm"><small>Must contain at least a digit</small></li>
{% endif %}
{% if server_settings.password_policy_special_chars %}
<li class="text-sm"><small>Must contain at least one of : {{ server_settings.password_policy_special_chars }}</small></li>
{% endif %}
</ul>
<div class="input-group mb-3">
{{ form.user_password(class='form-control', autocomplete="off") }}
<div class="input-group-append">
<span class="input-group-text">
<div class="user_show_password" id="toggle_user_password"><i class="fa-solid fa-eye"></i></div>
</span>
</div>
</div>
</div>
{% endif %}
{% if not user.user_id %}
<div class="form-group mt-3">
<div class="form-check">
<label class="form-check-label mt-3" >
{{ form.user_is_service_account(class="form-check-input", type="checkbox") }}
<span class="form-check-sign" id="formCheckIsServiceAccount"> Use as service account
<i class="ml-1 mt-1 fa-regular fa-circle-question" title="If checked, the user won't appear in the attribution suggestions and won't be able to connect on the UI" style="cursor:pointer;"></i>
</span>
</label>
</div>
</div>
{% endif %}
{% if user.user_id %}
<div class="form-group mt-3">
<label for="user_id" class="placeholder">User ID</label>
<input autocomplete="off" class="form-control" type="text" value="{{ user.user_id }}" disabled>
</div>
<div class="form-group mt-3">
<label for="user_uuid" class="placeholder">User UUID</label>
<input autocomplete="off" class="form-control" type="text" value="{{ user.user_uuid }}" disabled>
</div>
<div class="form-group mt-3">
<label for="user_uuid" class="placeholder">User API Key</label>
<input autocomplete="off" class="form-control" type="text" value="{{ user.user_api_key }}" disabled>
</div>
{% endif %}
</div>
{% if user.user_id %}
<button type="button" class="btn btn-danger mt-5"
onclick="delete_user('{{ user.user_id }}');">Delete</button>
{% if user.user_active %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="deactivate_user('{{ user.user_id }}');">Deactivate</button>
{% else %}
<button type="button" class="btn btn-outline-success mt-5"
onclick="activate_user('{{ user.user_id }}');">Activate</button>
{% endif %}
<button type="button" class="btn btn-outline-success ml-1 mt-5 float-right"
id="submit_new_user">Update</button>
<button type="button" class="btn btn-dark mt-5 float-right"
onclick="refresh_user_ac('{{ user.user_id }}');" id="users_refresh_ac_btn">Refresh access</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_user">Save</button>
{% endif %}
</form>
</div>
</div>
<div class="tab-pane" id="user_permissions_tab">
<div class="container col-md-12">
<p class="mb-4"><i class="fa-solid fa-circle-info mr-2"></i>Permissions are inherited from the groups the user belongs to. The table shows the effective permissions the user has on the platform.</p>
<table class="table table-striped" id="user_permissions_table">
<thead>
<tr>
<th>Permission</th>
<th>Value</th>
<th>Inherited from groups</th>
</tr>
</thead>
<tbody>
{% for perm in user.user_permissions %}
<tr>
<td>{{ user.user_permissions[perm].name }}</td>
<td>0x{{ perm | int(perm,16) }}</td>
<td>{% for group in user.user_permissions[perm].inherited_from %}<span class="badge ml-2 badge-light">{{ group }}</span>{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="user_groups_tab">
<div class="row">
<div class="container col-md-12">
<span class="ml-2 mt-3">Groups the user is member of.</span>
<button class="btn btn-dark btn-sm pull-right mr-2" onclick="manage_user_groups({{ user.user_id }});">
<span class="menu-title">Manage</span>
</button>
</div>
</div>
<div class="row mt-4">
<div class="container col-md-12">
<table class="table table-striped" id="user_groups_table">
<thead>
<tr>
<th>Group name</th>
<th>Group ID</th>
<th>Group UUID</th>
</tr>
</thead>
<tbody>
{% for group in user.user_groups %}
<tr>
<td>{{ group.group_name }}</td>
<td>{{ group.group_id }}</td>
<td>{{ group.group_uuid }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="tab-pane" id="user_cac_tab">
<div class="container col-md-12" >
<div class="row d-flex mb-4">
<div class="col-8">
<span>Cases accesses are usually inherited from groups memberships. These are not displayed here. This tab allows to add granular case access if necessary.</span>
</div>
<div class="col-4 pull-right">
<button class="btn btn-dark btn-sm pull-right" onclick="refresh_user_cac({{ user.user_id }});">
<span class="menu-title">Refresh</span>
</button>
<button class="btn btn-dark btn-sm pull-right mr-2" onclick="manage_user_cac({{ user.user_id }});">
<span class="menu-title" id="manage_user_cac_button">Set case access</span>
</button>
</div>
</div>
<div class="row mt-4">
<table class="table display table-striped table-hover responsive" width="100%" cellspacing="0" id="user_cac_table" >
<thead>
<tr>
<th>Case ID</th>
<th>Case Name</th>
<th>Access</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Case ID</th>
<th>Case Name</th>
<th>Access</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$('#toggle_user_password').on('click', function (e) {
const type = $('#user_password').attr('type') === 'password' ? 'text' : 'password';
$('#user_password').attr('type', type);
$('#toggle_user_password > i').attr('class', type === 'password' ? 'fa-solid fa-eye' : 'fa-solid fa-eye-slash');
});
$('#user_permissions_table').dataTable({
"order": [[ 1, "asc" ]]});
$('#user_groups_table').dataTable({
"order": [[ 1, "asc" ]],
"columns": [{
"title": "Group name",
"render": function ( data, type, row, meta ) {
if (type === 'display' ) {
return `<i class="fa-solid fa-trash-can mr-2 text-danger" style="cursor:pointer;" title="Remove from group" href="javascript:void(0)" onclick="remove_member_from_group_wrap('${row[1]}','{{ user.user_id }}')"></i>${sanitizeHTML(data)}`;
}
return data;
}},
{"title": "Group ID"},
{"title": "Group UUID"}
]
});
var modal_user_cac_table = $("#user_cac_table").DataTable({
dom: 'Blfrtip',
aaData: [],
aoColumns: [
{
"data": "case_id",
"render": function ( data, type, row ) {
return `<i class="fa-solid fa-trash-can mr-2 text-danger" style="cursor:pointer;" title="Remove access to case" href="javascript:void(0)" onclick="remove_case_access_from_user('{{ user.user_id }}',${data})"></i>${data}`;
},
"className": "dt-center"
},
{
"data": "case_name",
"className": "dt-center",
"render": function (data, type, row) {
return `<a target="_blank" rel="noopener" href="/case?cid=${row.case_id}">${sanitizeHTML(data)}</a>`;
}
},
{
"data": "access_level_list",
"render": function ( data, type, row ) {
ret_data = "";
for (acc in data) {
ret_data += `<span class="badge ml-2 badge-light">${data[acc].name}</span>`
}
return ret_data;
},
"className": "dt-center"
}
],
filter: true,
info: true,
ordering: true,
processing: true,
select: true
});
var actionOptionsUser = {
classes: [],
contextMenu: {
enabled: true,
isMulti: true,
xoffset: -10,
yoffset: -10,
headerRenderer: function (rows) {
if (rows.length > 1) {
return rows.length + ' items selected';
} else {
let row = rows[0];
return 'Quick action';
}
},
},
buttonList: {
enabled: false,
},
deselectAfterAction: true,
items: [],
};
actionOptionsUser.items.push({
type: 'option',
title: 'Remove access',
multi: true,
iconClass: 'fas fa-trash',
buttonClasses: ['btn', 'btn-outline-primary'],
action: function(rows){
remove_cases_access_from_user_table('{{ user.user_id }}', rows);
}
});
modal_user_cac_table.contextualActions(actionOptionsUser);
{% if user.user_id %}
current_user_cases_access_list = {{ user.user_cases_access|tojson }};
modal_user_cac_table.rows.add(current_user_cases_access_list);
modal_user_cac_table.columns.adjust().draw();
{% endif %}
</script>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->

View File

@ -0,0 +1,66 @@
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Set case access</h4>
<div class="row text-right">
<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 class="modal-body">
<div class="container col-md-12" >
<div class="row">
<div class="form-group" data-select2-id="7">
<label>Set cases access of user <i>{{ user.user_name }}</i> *</label>
<div class="col-12">
<select id="user_case_access_select" name="org_case_access_select" class="form-control ml-12" multiple="multiple"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
<select id="user_case_ac_select" name="org_case_ac_select" class="form-control ml-12"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="grant_case_access_to_user">Set access</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [];
$('#user_case_access_select').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select case',
emptyText: 'No case available to add',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#user_case_access_select').multiselect('dataprovider', [{% for ocs in outer_cases %}
{ label: "{{ ocs.case_name }}", value: {{ ocs.case_id }} }, {% endfor %}]);
$('#user_case_access_select').multiselect('refresh')
$('#user_case_ac_select').multiselect({
nonSelectedText: 'Select access level',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#user_case_ac_select').multiselect('dataprovider', [{% for acc in access_levels %}
{ label: "{{ acc.name }}", value: {{ acc.value }} }, {% endfor %}]);
$('#user_case_ac_select').multiselect('refresh');
</script>

View File

@ -0,0 +1,68 @@
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Set case access via groups</h4>
<div class="row text-right">
<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 class="modal-body">
<div class="container col-md-12" >
<div class="row">
<div class="form-group" data-select2-id="7">
<label>Set groups case access</label>
<div class="row">
<div class="col-12">
<select id="group_case_access_select" name="group_case_access_select" class="form-control select2-hidden-accessible ml-12" multiple="multiple"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
<select id="group_case_ac_select" name="group_case_ac_select" class="form-control select2-hidden-accessible ml-12"
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
onclick="set_case_access_via_group('{{ caseid }}')">Set access</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [];
$('#group_case_access_select').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select group(s)',
emptyText: 'No groups available to set',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_case_access_select').multiselect('dataprovider', [{% for ocs in groups %}
{ label: "{{ ocs.group_name }}", value: {{ ocs.group_id }} }, {% endfor %}]);
$('#group_case_access_select').multiselect('refresh')
$('#group_case_ac_select').multiselect({
nonSelectedText: 'Select access level',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#group_case_ac_select').multiselect('dataprovider', [{% for acc in access_levels %}
{ label: "{{ acc.name }}", value: {{ acc.value }} }, {% endfor %}]);
$('#group_case_ac_select').multiselect('refresh');
</script>

View File

@ -0,0 +1,65 @@
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading">
<div class="card-title">Case access</div>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-12">
<p>Click on a user to unveil access control path</p>
<table class="table table-striped table-bordered" id="case_audit_access_table">
<thead>
<tr>
<th>User ID</th>
<th>User Name</th>
<th>User UUID</th>
<th>Access</th>
</tr>
</thead>
<tbody>
{% for user_id in access_audit %}
<tr>
<td>
{% if access_audit[user_id].user_effective_access_list|length == 0 %}
<i class="mr-2 fa-solid text-danger fa-circle-xmark" title="No access"></i>
{% elif access_audit[user_id].user_effective_access_list|length == 1 %}
<i class="mr-2 fa-solid text-warning fa-circle-minus" title="Partial access"></i>
{% else %}
<i class="mr-2 fa-solid fa-circle-check text-success" title="All access"></i>
{% endif %}
{{ user_id }}
</td>
<td>{{ access_audit[user_id].user_info.user_name }}</td>
<td>{{ access_audit[user_id].user_info.user_uuid }}</td>
<td>{% if access_audit[user_id].user_effective_access_list|length == 0 %}
<span class="badge badge-pill badge-light" title="Click to show trace" href="#collapse_cac_{{user_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{user_id}}">No access</span>
{% else %}
{% for uac in access_audit[user_id].user_effective_access_list %}
<span class="badge badge-pill badge-light" title="Click to show trace" href="#collapse_cac_{{user_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{user_id}}">{{ uac }}</span>
{% endfor %}
<i class="fa-solid fa-eye ml-2" title="Click to show trace" href="#collapse_cac_{{user_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{user_id}}"></i>
{% endif %}
<ol class="activity-feed collapse" id="collapse_cac_{{user_id}}">
{% if access_audit[user_id].user_effective_access_list|length == 0 %}
The user is neither in a group nor an organisation who has access to this case
{% endif %}
{% for uac in access_audit[user_id].access_trace %}
<li class="feed-item {% if uac.state == 'Effective' %} feed-item-success {% else %} feed-item-danger {% endif %}" title="{{ uac.state }}">
<span class="text"><span class="badge badge-pill badge-light">{{ uac.name }}</span> ({{ uac.state }})</span><br />
<span class="text">Inherited from {{ uac.inherited_from.object_type }} <i class="fa-solid fa-right-long"></i> {{ uac.inherited_from.object_name }} (ID {{ uac.inherited_from.object_id }} :: UUID {{ uac.inherited_from.object_uuid }})</span>
</li>
{% endfor %}
</ol>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,62 @@
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Manage user groups</h4>
<div class="row text-right">
<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 class="modal-body">
<div class="container col-md-12" >
<div class="row">
<div class="form-group" data-select2-id="7">
<label>Groups membership *</label>
<div class="select2-input ml-12" data-select2-id="6">
<select id="user_groups_membership" name="user_groups_membership" class="form-control select2-hidden-accessible ml-12" multiple=""
tabindex="-1" aria-hidden="true" style="width: 100%">
</select>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="save_user_groups_membership">Save</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
var data = [];
if (current_groups_list.length === 0) {
refresh_groups();
}
for (group in current_groups_list) {
data.push({
label: `${current_groups_list[group].group_name}`,
value: current_groups_list[group].group_id
});
}
$('#user_groups_membership').multiselect({
buttonWidth: 400,
nonSelectedText: 'Select groups',
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder: 'Search',
filterBehavior: 'both',
widthSynchronizationMode: 'ifPopupIsSmaller'
});
$('#user_groups_membership').multiselect('dataprovider', data );
{% if user.user_groups %}
$('#user_groups_membership').multiselect('select', [
{% for group in user.user_groups %} {{ group.group_id }}, {% endfor %}
]);
$('#user_groups_membership').multiselect('refresh')
{% endif %}
</script>

View File

@ -0,0 +1,94 @@
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading">
<div class="card-title">User permissions</div>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-12">
<p class="mb-4">Permissions are inherited from the groups the user belongs to. The table shows the effective permissions the user has on the platform.</p>
<table class="table table-striped table-bordered" id="user_audit_permissions_table">
<thead>
<tr>
<th>Permission</th>
<th>Value</th>
<th>Inherited from groups</th>
</tr>
</thead>
<tbody>
{% for perm in permissions_audit['details'] %}
<tr>
<td>{{ permissions_audit['details'][perm].name }}</td>
<td>0x{{ perm | int(perm,16) }}</td>
<td>{% for group_id in permissions_audit['details'][perm].inherited_from %}<span class="badge ml-2 badge-light">{{ permissions_audit['details'][perm].inherited_from[group_id].group_name }}</span>{% endfor %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading">
<div class="card-title">User cases access</div>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-12">
<p>Click on a case to unveil access control path</p>
<table class="table table-striped table-bordered" id="user_audit_access_table">
<thead>
<tr>
<th>Case name</th>
<th>Access</th>
</tr>
</thead>
<tbody>
{% for case_id in access_audit %}
<tr>
<td>
{% if access_audit[case_id].user_effective_access|length == 0 or 'deny_all' in access_audit[case_id].user_effective_access[0].name %}
<i class="mr-2 fa-solid text-danger fa-eye-slash" title="No access"></i>
{% elif 'read_only' in access_audit[case_id].user_effective_access[0].name %}
<i class="mr-2 fa-solid text-warning fa-lock" title="Read only access"></i>
{% else %}
<i class="mr-2 fa-solid fa-circle-check text-success" title="Full access"></i>
{% endif %}
{{ access_audit[case_id].case_info.case_name }}
</td>
<td>{% if access_audit[case_id].user_effective_access|length == 0 %}
<span class="badge badge-pill badge-light" title="Click to show trace" href="#collapse_cac_{{case_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{case_id}}">No access</span>
{% else %}
{% for uac in access_audit[case_id].user_effective_access %}
<span class="badge badge-pill badge-light" title="Click to show trace" href="#collapse_cac_{{case_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{case_id}}">{{ uac.name }}</span>
{% endfor %}
<i class="fa-solid fa-eye ml-2" title="Click to show trace" href="#collapse_cac_{{case_id}}" style="cursor:pointer;" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_cac_{{case_id}}"></i>
{% endif %}
<ol class="activity-feed collapse" id="collapse_cac_{{case_id}}">
{% if access_audit[case_id].user_effective_access|length == 0 %}
The user is neither in a group nor an organisation who has access to this case
{% endif %}
{% for uac in access_audit[case_id].user_access %}
<li class="feed-item {% if uac.state == 'Effective' %} feed-item-success {% else %} feed-item-danger {% endif %}" title="{{ uac.state }}">
<span class="text">{% for ac in uac.access_list %}<span class="badge badge-pill badge-light">{{ ac.name }}</span>{% endfor %} ({{ uac.state }})</span><br />
<span class="text">Inherited from {{ uac.inherited_from.object_type }} <i class="fa-solid fa-right-long"></i> {{ uac.inherited_from.object_name }} (ID {{ uac.inherited_from.object_id }} :: UUID {{ uac.inherited_from.object_uuid }})</span>
</li>
{% endfor %}
</ol>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,82 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Attributes {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Objects Attributes</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_attribute_table();">
Refresh
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="hooks_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="attributes_table">
<thead>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tfoot>
<tr>
<th>#ID</th>
<th>Name</th>
<th>Description</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="modal_add_attribute" data-backdrop="true">
<div class="modal-xxl modal-dialog" role="document">
<div class="modal-content" id="modal_add_attribute_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal bg-primary-gradient" tabindex="-1" id="modal_preview_attribute" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_preview_attribute_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% 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/iris/manage.attributes.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,93 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Case Templates {% endblock title %}
{% block stylesheets %}
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Case Templates</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_case_template_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_case_template();">
Add case template
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="fire_upload_case_template();">
Upload case template
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="hooks_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table aria-describedby="Case template list"
class="table display table table-striped table-hover"
style="width: 100%; border-spacing: 0;" id="case_templates_table">
<thead>
<tr>
<th scope="col">#ID</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Added by</th>
<th scope="col">Created at</th>
<th scope="col">Updated at</th>
</tr>
</thead>
<tfoot>
<tr>
<th scope="col">#ID</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Added by</th>
<th scope="col">Created at</th>
<th scope="col">Updated at</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="modal_case_template" data-backdrop="true">
<div class="modal-xxl modal-dialog" role="document">
<div class="modal-content" id="modal_case_template_json">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_upload_case_template" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_upload_case_template_json">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% 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/iris/manage.case.templates.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,184 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Cases {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/dataTables.select.min.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-multiselect.min.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="card-title">Cases management</div>
</div>
<div class="card-body">
<div class="col-md-12">
<div class="row">
<div class="col d-flex justify-content-center">
<ul class="nav nav-pills nav-dark" id="pills-tabs-manage-case"
role="tablist">
<li class="nav-item submenu">
<a class="nav-link active show" id="add-tab" data-toggle="pill" href="#add" role="tab"
aria-controls="pills-home-nobd" aria-selected="true">New</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" id="view-tab" data-toggle="pill" href="#view" role="tab"
aria-controls="view-tab" aria-selected="false">Cases list</a>
</li>
</ul>
</div>
</div>
<div id="myTab1Content" class="tab-content col-md-12">
<div id="add" role="tabpanel" aria-labelledby="add-tab"
class="tab-pane fade px-4 py-5 show active">
<div class="row">
<div class="col-md-12 col-lg-6 col-sm-12">
<h4 class="border-bottom pb-3">General info</h4>
Fields with an asterix are required.
<form method="post" action='' id="form_new_case" autocomplete="off">
{{ form.hidden_tag() }}
<div class="mt-4 col-md-12 col-lg-12 col-sm-12">
<div class="input-group mb-4">
{{ form.case_customer(class="selectpicker form-control") }}
</div>
<div class="input-group mb-4">
{{ form.case_template_id(class="selectpicker form-control", data_actions_box="true") }}
</div>
<div class="input-group mb-4">
{{ form.classification_id(class="selectpicker form-control") }}
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">Case name *</span>
</div>
{{ form.case_name(class="form-control", type="text") }}
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">Short description *</span>
</div>
{{ form.case_description(class="form-control", type="text") }}
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">SOC ticket ID</span>
</div>
{{ form.case_soc_id(class="form-control", type="text") }}
</div>
</div>
</form>
</div>
<div class="col-md-12 col-lg-6 col-sm-12">
{% if attributes and attributes|length > 0 %}
<h4><ul class="nav nav-tabs nav-lines mr-4" role="tablist">
{% for ca in attributes %}
<li class="nav-item submenu">
<a class="nav-link {% if loop.index == 1 %}{{"active show"}}{% endif %}" data-toggle="tab" href="#itab_{{ loop.index }}_{{ ca.lower() | replace(' ', '_' ) }}"
role="tab" aria-selected="false">{{ca}}</a>
</li>
{% endfor %}
</ul></h4>
<div role="tabpanel">
<div class="tab-content">
{% set is_case_page = True %}
{% include 'modals/modal_attributes_tabs.html' %}
</div>
</div>
{% endif %}
</div>
</div>
<button class="btn btn-outline-success ml-4 mt-5 float-right" id="submit_new_case_btn" onclick="submit_new_case();">Create</button>
</div>
<div id="view" role="tabpanel" aria-labelledby="view-tab" class="tab-pane fade px-4 py-5">
<button type="button" class="btn btn-sm btn-outline-dark float-right mr-3"
onclick="refresh_case_table();">
Refresh
</button>
<div class="table-responsive" id="cases_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display wrap col-border table-striped table-hover" width="100%"
cellspacing="0" id="cases_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Customer</th>
<th>State</th>
<th>Open date</th>
<th>Close date</th>
<th>SOC Ticket</th>
<th>Opening user</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
<th>Customer</th>
<th>State</th>
<th>Open date</th>
<th>Close date</th>
<th>SOC Ticket</th>
<th>Opening user</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- End bordered tabs -->
</div>
</div>
</div>
</div>
</div>
</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>
{% endif %}
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/core/jquery.validate.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.cellEdit.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.buttons.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.select.min.js"></script>
<script src="/static/assets/js/plugin/datatables/dataTables.responsive.min.js"></script>
<script src="/static/assets/js/plugin/datatables/buttons.html5.min.js"></script>
<script src="/static/assets/js/plugin/datatables/buttons.print.min.js"></script>
<script src="/static/assets/js/plugin/datatables/buttons.print.min.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 src="/static/assets/js/iris/datatablesUtils.js"></script>
<script src="/static/assets/js/iris/manage.cases.common.js"></script>
<script src="/static/assets/js/iris/manage.cases.js"></script>
<script>
$('form#form_new_case').validate();
</script>
{% endblock javascripts %}

View File

@ -0,0 +1,191 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Customers {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<a class="mb-2 ml-1 text-dark" href="/manage/customers?cid={{ session['current_case'].case_id }}"><i class="fa-solid fa-arrow-left"></i> Back</a>
<div class="mt-2 mb-4">
<div class="row ml-2 mr-2">
<h2 class="pb-2">Customer > {{ customer.customer_name }}</h2>
<button class="btn btn-light btn-sm ml-auto" onclick="customer_detail('{{ customer.customer_id }}');">Edit customer</button>
</div>
</div>
<input id="customer_id" style="display:none;" value="{{ customer.customer_id }}"/>
<div class="row">
<div class="col-md-2">
<div class="card card-dark bg-success-gradient">
<div class="card-body pb-0">
<div class="h1 fw-bold float-right"></div>
<h2 id="current_open_cases" class="mb-2">0</h2>
<p>Current open cases</p>
<div class="pull-in sparkline-fix chart-as-background">
<div id="chart_current_open_cases"></div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card card-dark bg-info-gradient">
<div class="card-body pb-0">
<div class="h5 fw-bold float-right"><span id="ratio_month"></span></div>
<h2 id="cases_current_month" class="mb-2"></h2>
<p>Current month</p>
<div class="pull-in sparkline-fix chart-as-background">
<div id="chart_month_cases"></div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card card-dark bg-info-gradient">
<div class="card-body pb-0">
<div class="h1 fw-bold float-right"></div>
<h2 id="cases_last_month" class="mb-2">0</h2>
<p>Last month</p>
<div class="pull-in sparkline-fix chart-as-background">
<div id=""></div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card card-dark bg-info-gradient">
<div class="card-body pb-0">
<div class="h5 fw-bold float-right"><span id="ratio_year"></span></div>
<h2 id="cases_current_year" class="mb-2">0</h2>
<p>Current year </p>
<div class="pull-in sparkline-fix chart-as-background">
<div id="chart_year_cases"></div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card card-dark bg-info-gradient">
<div class="card-body pb-0">
<div class="h3 fw-bold float-right"></div>
<h2 id="cases_last_year" class="mb-2">0</h2>
<p>Last year (<span id="last_year"></span>)</p>
<div class="pull-in sparkline-fix chart-as-background">
<div></div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card card-dark bg-close-gradient">
<div class="card-body pb-0">
<div class="h1 fw-bold float-right"></div>
<h2 id="cases_total" class="mb-2">0</h2>
<p>Total</p>
<div class="pull-in sparkline-fix chart-as-background">
<div></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card card-customer">
<div class="card-body">
<div class="row">
<div class="col-md-3 info-customer">
<h5 class="sub"><b>Customer name</b></h5>
<p>{{ customer.customer_name }}</p>
</div>
<div class="col-md-3 info-customer">
<h5 class="sub"><b>Customer Description</b></h5>
<p>{{ customer.customer_description }}</p>
</div>
<div class="col-md-3 info-customer">
<h5 class="sub text-bold"><b>Customer SLAs</b></h5>
<p>{{ customer.customer_sla }}</p>
</div>
<div class="col-md-3 info-customer">
<h5 class="sub text-bold"><b>Average case duration</b></h5>
<p><span id="average_case_duration"></span> days</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card card-customer">
<div class="card-header">
<div class="row">
<div class="col-12">
<div class="row">
<h3><strong><i class="ml-1 fa-regular fa-address-book mr-1"></i> Contacts</strong></h3>
<button class="btn btn-light btn-sm ml-auto" onclick="add_new_contact({{ customer.customer_id }});">Add Contact</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<div class="customer-list">
{% for contact in contacts %}
<div class="contact-list-item">
<div class="contact-list-detail">
<span class="date float-right"><button class="btn btn-light btn-sm" onclick="edit_contact('{{ contact.id }}','{{ customer.customer_id }}');">Edit</button></span>
<span class="h4">{{ contact.contact_name }}</span>
<p class="ml-2">
{% if contact.contact_role %}
<b>Role: </b>{{ contact.contact_role }}<br/>
{% endif %}
{% if contact.contact_email %}
<b>Email: </b>{{ contact.contact_email }}<br/>
{% endif %}
{% if contact.contact_work_phone %}
<b>Work phone: </b>{{ contact.contact_work_phone }}<br/>
{% endif %}
{% if contact.contact_mobile_phone %}
<b>Mobile phone: </b>{{ contact.contact_mobile_phone }}<br/>
{% endif %}
{% if contact.contact_note %}
<b>Notes: </b>{{ contact.contact_note }}<br/>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="modal" tabindex="-1" role="dialog" id="modal_add_customer" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_add_customer_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_add_contact" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_add_contact_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/iris/manage.customers.js"></script>
<script src="/static/assets/js/iris/view.customers.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,74 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Customers {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Customers management</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_customer_table(true);">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_customer();">Add customer</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="customers_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="customers_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_add_customer" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_add_customer_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
</div>
{% endif %}
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/iris/manage.customers.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>IRIS - Server updates</title>
<meta content='width=device-width, initial-scale=1.0, shrink-to-fit=no' name='viewport' />
<link rel="icon" href="/static/assets/img/logo.ico" type="image/x-icon"/>
<!-- Fonts and icons -->
<script src="/static/assets/js/plugin/webfont/webfont.min.js"></script>
<script>
WebFont.load({
custom: {"families":["Lato:300,400,700,900", "Flaticon", "Font Awesome 5 Solid", "Font Awesome 5 Regular", "Font Awesome 5 Brands", "simple-line-icons"],
urls: ['/static/assets/css/fonts.css']},
active: function() {
sessionStorage.fonts = true;
}
});
</script>
<!-- CSS Files -->
<link rel="stylesheet" href="/static/assets/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/assets/css/atlantis.css">
<link rel="stylesheet" href="/static/assets/css/bootstrap-select.min.css">
</head>
<body class="bg-dark-gradient p-4" style="height: 100vh;">
<div class="container bg-white shadow-lg p-3 rounded mb-4">
<div class="mt-2 row">
<div class="col d-flex justify-content-center">
<a href="/" class="logo ml-2 text-center">
<img src="/static/assets/img/logo-alone-2-black.png" alt="navbar brand" class='image-update navbar-brand' width="50">
</a>
</div>
</div>
<div class="mt-2 row">
<div class="col d-flex justify-content-center">
<h1 class="text-dark text-center">Server Updates</h1>
</div>
</div>
<div class="row ">
<div class="col d-flex justify-content-center">
<i class="text-dark">Some great improvements are hopefully on their way, hang tight</i>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-center">
<h4 class="text-danger text-center">Please do not leave or refresh the page once updates have started</h4>
</div>
</div>
<div class="row mt-4 update_start_txt">
<div class="col pl-4 justify-content-center">
<p class="text-center">Ensure users are disconnected. Any unsaved work might be lost.</p>
</div>
</div>
<div class="row mt-4 justify-content-center update_start_txt">
<div class="col-6">
<p class="text-center">The following steps might occur during updates, depending on the updates scope : </p>
<ul>
<li>Fetch of updates information from github.com</li>
<li>Download of updates assets from github</li>
<li>Signatures verification</li>
<li>Compatibility verifications</li>
<li>Back up of the current server code to backup storage defined in <code>BACKUP_PATH</code></li>
<li>Server stop and update of application</li>
<li>Server restarts</li>
<li>Version check</li>
</ul>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-center ">
<a href='#' onclick="start_updates();" class="btn btn-outline-success text-center" id="update_start_btn">Start updates</a>
</div>
</div>
<div id="container-updates" class="row shadow-lg ml-4 mr-4 rounded" style="display:none;">
<div class="col justify-content-left text-dark pl-4 pb-4 pt-4 pr-4 mh-100">
<div class="col">
<div id="updates_log">
</div>
<div id="updates_log_end"></div>
</div>
</div>
</div>
<div class="row text-dark">
<div class="col d-flex justify-content-center">
<div id="offline_time" style="display:none;"></div>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-center">
<div id="tag_bottom" style="display:none;" class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
<a href='/manage/settings' class="btn btn-dark ml-mr-auto" style="display:none;" id="update_return_button">Go back to server</a>
</div>
</div>
</div>
<!-- Core JS Files -->
<script src="/static/assets/js/core/jquery.3.2.1.min.js"></script>
<script src="/static/assets/js/core/popper.min.js"></script>
<script src="/static/assets/js/core/bootstrap.min.js"></script>
<!-- jQuery UI -->
<script src="/static/assets/js/plugin/jquery-ui-1.12.1.custom/jquery-ui.min.js"></script>
<script src="/static/assets/js/plugin/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"></script>
<!-- jQuery Scrollbar -->
<script src="/static/assets/js/plugin/jquery-scrollbar/jquery.scrollbar.min.js"></script>
<!-- Atlantis JS -->
<script src="/static/assets/js/atlantis.min.js"></script>
{% if current_user.in_dark_mode %}<script src="/static/assets/js/dark-mode.js"></script>{% endif %}
<script src="/static/assets/js/core/socket.io.js"></script>
<script src="/static/assets/js/iris/updates.handler.js"></script>
</body>
</html>

View File

@ -0,0 +1,143 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Modules {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
<link rel="stylesheet" href="/static/assets/css/dataTables.group.min.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Modules management</div>
{{ form.hidden_tag() }}
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_modules();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_module();">Add module</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="module_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="modules_table">
<thead>
<tr>
<th>#ID</th>
<th>Module name</th>
<th>Has pipeline</th>
<th>Module version</th>
<th>Interface version</th>
<th>Date added</th>
<th>Added by</th>
<th>Active</th>
</tr>
</thead>
<tfoot>
<tr>
<th>#ID</th>
<th>Module name</th>
<th>Has pipeline</th>
<th>Module version</th>
<th>Interface version</th>
<th>Date added</th>
<th>Added by</th>
<th>Active</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Registered hooks</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-icon btn-round btn-outline-black float-right" onclick="refresh_modules_hooks();">
<i class="fas fa-sync rotate"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="hooks_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="hooks_table">
<thead>
<tr>
<th>#ID</th>
<th>Registrant module</th>
<th>Hook name</th>
<th>Hook description</th>
<th>Automatic trigger</th>
<th>Active</th>
</tr>
</thead>
<tfoot>
<tr>
<th>#ID</th>
<th>Registrant module</th>
<th>Hook name</th>
<th>Hook description</th>
<th>Automatic trigger</th>
<th>Active</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" role="dialog" id="modal_add_module" data-backdrop="true">
<div class="modal-xl modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content" id="modal_add_module_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<div class="modal bg-shadow-gradient" tabindex="-1" role="dialog" id="modal_update_param" data-backdrop="true">
<div class="modal-xl modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">
<div class="modal-content shadow-lg p-3 mb-5 rounded" id="modal_update_param_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
</div>
{% 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/plugin/datatables/dataTables.group.min.js"></script>
<script src="/static/assets/js/iris/manage.modules.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,209 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Objects {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-heading collapsed" href="#collapse_assets" title="Click to unfold" data-toggle="collapse" aria-expanded="false" role="button" aria-controls="collapse_assets">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">Assets types</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_asset_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_asset_type();">Add new</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_assets">
<div class="table-responsive" id="assets_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="assets_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Icon - not compromised</th>
<th>Icon - compromised</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
<th>Icon - not compromised</th>
<th>Icon - compromised</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header" >
<div class="row">
<div class="col col-heading collapsed" href="#collapse_ioc" title="Click to unfold" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_ioc">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">IOC types</div>
</div>
<div class="col float-right">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_ioc_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_ioc_type();">Add new</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_ioc">
<div class="table-responsive" id="ioc_table_wrapper">
<div class="selectgroup">
<span id="ioc_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="ioc_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Taxonomy</th>
<th>Validation regex</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
<th>Taxonomy</th>
<th>Validation regex</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header" >
<div class="row">
<div class="col col-heading collapsed" href="#collapse_classifications" title="Click to unfold" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_classifications">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">Case classifications</div>
</div>
<div class="col float-right">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_classification_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_classification();">Add new</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_classifications">
<div class="table-responsive" id="classification_table_wrapper">
<div class="selectgroup">
<span id="classification_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="classification_table">
<thead>
<tr>
<th>Name</th>
<th>Expanded name</th>
<th>Description</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Expanded name</th>
<th>Description</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header" >
<div class="row">
<div class="col col-heading collapsed" href="#collapse_state" title="Click to unfold" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="collapse_state">
<span class="accicon float-left mr-3"><i class="fas fa-angle-right rotate-icon"></i></span>
<div class="card-title">Case state</div>
</div>
<div class="col float-right">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_state_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_state();">Add new</button>
</div>
</div>
</div>
<div class="card-body collapse" id="collapse_state">
<div class="table-responsive" id="state_table_wrapper">
<div class="selectgroup">
<span id="state_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="state_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" tabindex="-1" id="modal_add_type" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_add_type_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% endif %}
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/iris/manage.objects.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,218 @@
{% extends "layouts/default.html" %}
{% block title %} Server Settings {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row ">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="card-title">Server versions</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-12">
<div class="col-12 mb-4">
<dl class="row">
<dt class="col-sm-3">IRIS server version:</dt>
<dd class="col-sm-8">{{ versions.iris_current }}</dd>
<dt class="col-sm-3">Database revision:</dt>
<dd class="col-sm-8">{{ versions.db_revision }}</dd>
<dt class="col-sm-3">Min. API version supported:</dt>
<dd class="col-sm-8">{{ versions.api_min }}</dd>
<dt class="col-sm-3">Max. API version supported:</dt>
<dd class="col-sm-8">{{ versions.api_current }}</dd>
<dt class="col-sm-3">Min. module interface version supported:</dt>
<dd class="col-sm-8">{{ versions.interface_min }}</dd>
<dt class="col-sm-3">Max. module interface version supported:</dt>
<dd class="col-sm-8">{{ versions.interface_current }}</dd>
</dl>
</div>
</div>
</div>
<!-- <div class="row">-->
<!-- <div class="col-12">-->
<!-- <div class="float-right mt-4">-->
<!-- <button class="btn btn-outline-primary" id="check_updates" type="button" onclick="check_updates()">Check for updates</button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="card-title">Global settings</div>
</div>
<div class="card-body">
<div class="row mt-2">
<div class="col-12">
<form id="form_srv_settings">
{{ form.hidden_tag() }}
<h2>Proxy</h2>
<div class="mb-4">
<p>Proxy settings can be used by modules to access external resources.</p>
<div class="row mb-4">
<div class="col-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
HTTP Proxy
</span>
</div>
<input class="form-control" name="http_proxy" placeholder="HTTP Proxy" value="{{ settings.http_proxy }}">
</div>
</div>
<div class="col-6">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
HTTP Proxy
</span>
</div>
<input class="form-control" name="https_proxy" placeholder="HTTPS Proxy" value="{{ settings.http_proxy }}">
</div>
</div>
</div>
</div>
<h2 class="mt-4">Post-init Behavior</h2>
<div class="col-12 mb-4">
<!-- <div class="form-check">-->
<!-- <label class="form-check-label">-->
<!-- <input class="form-check-input" type="checkbox" id="enable_updates_check" name="enable_updates_check" {% if settings.enable_updates_check %}checked{% endif %}>-->
<!-- <span class="form-check-sign">Enable automatic daily check for updates</span>-->
<!-- </label>-->
<!-- </div>-->
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="prevent_post_mod_repush" name="prevent_post_mod_repush" {% if settings.prevent_post_mod_repush %}checked{% endif %}>
<span class="form-check-sign">Prevent post-init step to register default modules again during boot</span>
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="prevent_post_objects_repush" name="prevent_post_objects_repush" {% if settings.prevent_post_objects_repush %}checked{% endif %}>
<span class="form-check-sign">Prevent post-init step to register default case objects again during boot</span>
</label>
</div>
</div>
<h2 class="mt-4">Password Policy</h2>
<p>A password policy change applies to new or updated passwords.</p>
<div class="row mb-2">
<div class="col-4">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Minimum Password Length
</span>
</div>
<input class="form-control" name="password_policy_min_length" placeholder="12" type="number" value="{{ settings.password_policy_min_length }}">
</div>
</div>
<div class="col-8">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Include Special chars
</span>
</div>
<input class="form-control" name="password_policy_special_chars" placeholder="Set empty to disable" type="text" value="{{ settings.password_policy_special_chars }}">
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="password_policy_upper_case" name="password_policy_upper_case" {% if settings.password_policy_upper_case %}checked{% endif %}>
<span class="form-check-sign">Must include uppercase char</span>
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="password_policy_lower_case" name="password_policy_lower_case" {% if settings.password_policy_lower_case %}checked{% endif %}>
<span class="form-check-sign">Must include lowercase char</span>
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" id="password_policy_digit" name="password_policy_digit" {% if settings.password_policy_digit %}checked{% endif %}>
<span class="form-check-sign">Must include digits</span>
</label>
</div>
</div>
</div>
</form>
<div class="float-right mt-4">
<button class="btn btn-outline-primary float-right" id="save_srv_settings" type="button" onclick="update_settings()">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="card-title">Backups</div>
</div>
<div class="card-body">
<div class="row mt-2">
<div class="col-12">
<h2>Database</h2>
<p>Initiate a database backup. The backup file is stored on the configured path <code>BACKUP_PATH</code></p>
<div class="float-right mt-4">
<button class="btn btn-outline-primary" id="init_db_backup" type="button" onclick="init_db_backup()">Backup database</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal " tabindex="-1" role="dialog" id="modal_updates" data-backdrop="true">
<div class="modal-xl modal-dialog" role="document">
<div class="modal-content" id="modal_updates_content">
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Server Updates</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">
<h3>Please wait while we look for updates</h3>
</div>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/plugin/showdown/showdown.min.js"></script>
<script src="/static/assets/js/iris/manage.server.settings.js"></script>
<script>
</script>
{% endblock javascripts %}

View File

@ -0,0 +1,82 @@
{% extends "layouts/default.html" %}
{% block title %} Manage Templates {% endblock title %}
{% block stylesheets %}
<link rel="stylesheet" href="/static/assets/css/suggestags.css">
{% endblock stylesheets %}
{% block content %}
{% if current_user.is_authenticated %}
{{ form.hidden_tag() }}
<div class="page-inner">
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<div class="card-title">Report Templates Management</div>
</div>
<div class="col">
<button type="button" class="btn btn-sm btn-dark float-right ml-2" onclick="refresh_template_table();">
Refresh
</button>
<button class="btn btn-sm btn-dark float-right ml-2" onclick="add_report_template();">Add template</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive" id="assets_table_wrapper">
<div class="selectgroup">
<span id="table_buttons"></span>
</div>
<table class="table display table table-striped table-hover" width="100%"
cellspacing="0" id="reports_table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Naming format</th>
<th>Date created</th>
<th>Created by</th>
<th>Language</th>
<th>Type</th>
<th>Download</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Name</th>
<th>Description</th>
<th>Naming format</th>
<th>Date created</th>
<th>Created by</th>
<th>Language</th>
<th>Type</th>
<th>Download</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade " tabindex="-1" role="dialog" id="modal_add_report_template" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content" id="modal_report_template_content">
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% endif %}
{% endblock content %}
{% block javascripts %}
<script src="/static/assets/js/iris/manage.templates.js"></script>
{% endblock javascripts %}

View File

@ -0,0 +1,80 @@
<div class="modal-header">
<h4>Add Asset Type</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_asset_type" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="asset_type" class="mr-4">Asset Type
</label>
{{ form.asset_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="asset_description" class="placeholder">Asset Type description</label>
{{ form.asset_description(class='form-control', autocomplete="off") }}
</div>
<div class="row ml-2">
{% if assettype.asset_icon_not_compromised %}
<div class="form-group col-2">
<label for="asset_icon_not_compromised" class="mr-4">Preview</label>
<div id="asset_icon_not_compromised">
<img style="width:2em;height:2em" src="{{ assettype.asset_icon_not_compromised_path }}">
</div>
</div>
<div class="form-group col">
<label for="asset_icon_compromised" class="mr-4">Asset Icon - not compromised
</label>
{{ form.asset_icon_not_compromised(class='form-control',type='file', accept='.svg, .png') }}
</div>
{% else %}
<div class="form-group col">
<label for="asset_icon_compromised" class="mr-4">Asset Icon - not compromised
</label>
{{ form.asset_icon_not_compromised(class='form-control',type='file', required='false', value=assettype.asset_icon_not_compromised, accept='.svg, .png') }}
</div>
{% endif %}
</div>
<div class="row ml-2">
{% if assettype.asset_icon_compromised %}
<div class="form-group col-2">
<label for="asset_icon_compromised" class="mr-4">Preview</label>
<div id="asset_icon_compromised">
<img style="width:2em;height:2em" src="{{ assettype.asset_icon_compromised_path }}">
</div>
</div>
<div class="form-group col">
<label for="asset_icon_compromised" class="mr-4">Asset Icon - compromised
</label>
{{ form.asset_icon_compromised(class='form-control',type='file', accept='.svg, .png') }}
</div>
{% else %}
<div class="form-group col">
<label for="asset_icon_compromised" class="mr-4">Asset Icon - compromised
</label>
{{ form.asset_icon_compromised(class='form-control', type='file', required='false', value=assettype.asset_icon_compromised, accept='.svg, .png') }}
</div>
{% endif %}
</div>
{% if assettype.asset_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_asset_type('{{ assettype.asset_id }}');">Delete</button>
<button type="submit" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_assettype">Update</button>
{% else %}
<button type="submit" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_assettype">Save</button>
{% endif %}
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,154 @@
<div class="modal-header">
<h4>Edit attribute {{ attribute.attribute_display_name }}</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_attribute" enctype="multipart/form-data">
<div class="row">
<div class="col-6">
<div class="accordion accordion-primary">
<h2>{{ attribute.attribute_display_name }} attributes</h2>
<p>Attributes allow to extend the fields available for analysts when they add or edit {{ attribute.attribute_display_name }}.</p>
<div class="alert-std alert-warning" role="alert">
Attributes can be added by administrator in this UI, or they can be pushed by modules.<br/>
This means each {{ attribute.attribute_display_name }} object may have a different set of attributes.
Updating the default objects here will result in an update of every existing object, which might take a huge amount of time.
<p>Typing wrong attributes here might result in UI breaks. IRIS will attempt to validate the attributes' taxonomy before committing.</p>
<b>To avoid this, use the Preview button before saving. It displays a 1-to-1 UI representation of the attributes</b>
</div>
<div class="card">
<div class="card-header collapsed" id="drop_attr_description" data-toggle="collapse" data-target="#drop_attr_desc" aria-expanded="false" aria-controls="drop_attr_desc" role="button">
<div class="span-icon">
<div class="flaticon-tea-cup"></div>
</div>
<div class="span-title">
More details
</div>
<div class="span-mode"></div>
</div>
<div id="drop_attr_desc" class="collapse" aria-labelledby="drop_tax_attr" style="">
<div class="card-body">
<p>These attributes are stored in each {{ attribute.attribute_display_name }} object in the form of a JSON structure.</p>
<p>Attributes in this page represent the default attributes of each new {{ attribute.attribute_display_name }} objects. Existing object are updated if they
don't hold the specified attributes. <b>Other existing attributes are not deleted.</b></p>
Attributes can have the following purposes:
<ul>
<li><b>Inputs</b>: Offer analysts the possibility to fill additional details. Multiple types of inputs are supported. See taxonomy for more details</li>
<li><b>Raw</b>: A static content rendered in raw text. HTML is not interpreted.</li>
<li><b>HTML</b>: A static content rendered as HTML for infinite possibilities. <b>Careful, this is by nature prone to vulnerabilities.</b></li>
</ul>
</div>
</div>
</div>
<div class="card">
<div class="card-header collapsed" id="drop_attr_taxonomy" data-toggle="collapse" data-target="#drop_tax_attr" aria-expanded="false" aria-controls="drop_tax_attr" role="button">
<div class="span-icon">
<div class="flaticon-pencil"></div>
</div>
<div class="span-title">
Attributes taxonomy
</div>
<div class="span-mode"></div>
</div>
<div id="drop_tax_attr" class="collapse" aria-labelledby="drop_tax_attr" style="">
<div class="card-body">
Attributes are defined as below.
<pre>
{
"Tab Name 1": { // Defines a new tab in the {{ attribute.attribute_display_name }} modal
"Field 1": { // Defines a new field within the Tab Name 1
"type": "input_string", // Defines the type of field, here a standard string input
"mandatory": true, // Indicates whether the field is mandatory upon saving
"value": "" // Default value if any, else empty
},
"Field 2": { // Defines a second field within the tab Tab Name 1
"type": "input_checkbox", // Defines an input checkbox
"mandatory": false, // Indicates whether the field is mandatory upon saving
"value": true // Default value
}
},
"VT report": { // Defines a second tab named VT report
"Content": { // Defines a new field Content within the VT Report
"type": "html", // Defines an HTML interpreted content
"value": "" // Default value if any, else empty
}
}
}
</pre>
<h4>Field types</h4>
The supported fields types are:
<ul>
<li>input_string: Standard input text</li>
<li>input_textfield: Standard input textfield</li>
<li>input_checkbox: Standard checkbox</li>
<li>input_date: Standard date input</li>
<li>input_datetime: Standard date and time input</li>
<li>input_select: Standard select input. Need "options" tag to describe the available options</li>
<li>raw: A static content rendered in raw text. HTML is not interpreted.</li>
<li>html: A static content rendered as HTML. <b>Careful, this is by nature prone to vulnerabilities.</b></li>
</ul>
</div>
</div>
</div>
<div class="card">
<div class="card-header collapsed" id="drop_attr_over" data-toggle="collapse" data-target="#drop_attr_overwrite" aria-expanded="false" aria-controls="drop_attr_overwrite" role="button">
<div class="span-icon">
<div class="flaticon-exclamation"></div>
</div>
<div class="span-title">
Overwrite features
</div>
<div class="span-mode"></div>
</div>
<div id="drop_attr_overwrite" class="collapse" aria-labelledby="drop_tax_attr" style="">
<div class="card-body">
<p>Changing types of fields in attributes might result in incompatibles types and existing objects being unable to be migrated.</p>
<p>When this happens, IRIS will not update the fields of these objects and let them as is to prevent any data loss.</p>
<p>This behavior can however be changed by using the <kbd>Complete overwrite</kbd> and <kbd>Partial overwrite</kbd> buttons.</p>
<p><kbd>Partial overwrite</kbd> basically resets the attributes values of every {{ attribute.attribute_display_name }} objects that matches the current ones, and then applies the current attributes.
All associated values are lost. This does not impact attributes pushed by modules.</p>
<p><kbd>Complete overwrite</kbd> resets all attributes of every {{ attribute.attribute_display_name }} objects, including the ones created by modules, and then applies the current attributes.
All associated values are lost.</p>
<b>In any case, none of the native values of the {{ attribute.attribute_display_name }} objects are modified. This only concerns custom attributes.</b>
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="Attribute content" class="mr-4">Attribute definition
</label>
<div id="editor_detail">{{ attribute.attribute_content|tojsonsafe }}</div>
</div>
</div>
</div>
</form>
</div>
<div class="alert alert-warning" style="display:none;" role="alert" id="alert_attributes_details">
<span id="alert_attributes_edit"></span><br/>
<b>Logs:</b>
<ul id="attributes_err_details_list">
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger float-right" id="submit_complete_overwrite">Complete overwrite</button>
<button type="button" class="btn btn-outline-danger mr-auto" id="submit_partial_overwrite">Partial overwrite</button>
<button type="button" class="btn btn-outline-black float-right" id="preview_attribute">Preview</button>
<button type="button" class="btn btn-outline-success float-right" id="submit_new_attribute">Update</button>
</div>

View File

@ -0,0 +1,72 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col">
<h4 class="modal-title mt-1 mr-4">{% if customer.client_id %}Edit customer #{{ customer.client_id }}{% else %}Add customer{% endif %}</h4>
<small><a class="text-muted">{% if customer.client_uuid %}#{{ customer.client_uuid }}{% endif %}</a></small>
</div>
{% include 'modals/modal_attributes_nav.html' %}
<div class="col ">
<div class="row float-right">
<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_customer" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="customer" class="mr-4">Name *</label>
{{ form.customer_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group">
<label for="description" class="mr-4">Description</label>
{{ form.customer_description(class='form-control', autocomplete="off") }}
</div>
<div class="form-group">
<label for="sla" class="mr-4">SLAs</label>
{{ form.customer_sla(class='form-control', autocomplete="off") }}
</div>
</div>
</form>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
{% if customer.client_id %}
<button
type="button"
class="btn btn-outline-danger mt-5"
onclick="delete_customer('{{ customer.client_id }}');"
>
Delete
</button>
<button
type="button"
class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_customer"
>
Update
</button>
{% else %}
<button
type="button"
class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_customer"
>
Save
</button>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,49 @@
<div class="modal-header">
<h4>Add IOC Type</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_ioc_type" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="type_name" class="mr-4">Type name
</label>
{{ form.type_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="type_description" class="placeholder">Type description</label>
{{ form.type_description(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="type_taxonomy" class="placeholder">Type taxonomy</label>
{{ form.type_taxonomy(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="type_description" class="placeholder">Type validation regex</label>
{{ form.type_validation_regex(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="type_description" class="placeholder">Type validation expect explanation</label>
{{ form.type_validation_expect(class='form-control', autocomplete="off") }}
</div>
{% if ioc_type.type_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_ioc_type('{{ ioc_type.type_id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_ioc_type">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_ioc_type">Save</button>
{% endif %}
</form>
</div>
</div>

View File

@ -0,0 +1,40 @@
<div class="modal-header">
<h4>Add Module</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_module">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<h3>Module extensions</h3>
<p>Iris can be extended with compatible modules. Modules are pip packages providing a specific interface which allows
Iris and the module to communicate. Enter below a pip package name already installed.</p>
<p>Ex: iris_evtx</p>
<i class="">Tips: you can develop your own module by following <a target=”_blank” href="https://docs.dfir-iris.org/development/modules/" >this link</a></i>
<div class="mt-4">
<strong class="text-danger">Note :</strong> Modules are running as the same trust level as Iris. They can thus access all the information
stored on Iris as well as run code on the server. Please be cautious and review modules before installing them.
</div>
<label for="module_name" class="mr-4 mt-3">Module name</label>
{{ form.module_name(class='form-control', autocomplete="off") }}
<div class="alert-std alert-warning" style="display:none;" role="alert" id="alert_mod_add">
</div>
<div class="alert-std alert-warning" style="display:none;" role="alert" id="alert_mod_details">
<b>Logs</b>
<ul id="details_list">
</ul>
</div>
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_module">Validate module</button>
</form>
</div>
</div>

View File

@ -0,0 +1,75 @@
<div class="modal-header">
<h4>Add Report Template</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_report_template" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group mt-3">
<label for="report_name" class="placeholder">Template name</label>
{{ form.report_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="report_type" class="mr-4">Template type
</label>
{{ form.report_type(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="report_language" class="mr-4">Template language
</label>
{{ form.report_language(class="selectpicker pl--6 col-md-12") }}
</div>
</div>
</div>
</div>
<div class="form-group mt-3">
<label for="report_description" class="placeholder">Template description</label>
{{ form.report_description(class='form-control', autocomplete="off") }}
</div>
<div class="form-group ml--2">
{{ form.report_language(class="selectpicker pl--6") }}
</div>
<div class="form-group mt-3">
<label for="report_name_format" class="placeholder">Template name format</label>
<br/>This is the name of the generated report file. Do not input extension.
Available tags:
<ul>
<li>%date% : date of generation</li>
<li>%customer% : customer name</li>
<li>%case_name% : name of the case</li>
<li>%code_name%: autogenerated enterprise code name</li>
</ul>
Example: <i>analysis_%customer%_%date%</i>
{{ form.report_name_format(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="report_name_format" class="placeholder">Template file : </label>
<input type=file name=file>
</div>
{% if report_template.asset_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_report_template('{{ assettype.asset_id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_report_template">Update</button>
{% else %}
<button type="submit" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_report_template">Save</button>
{% endif %}
</form>
</div>
</div>

View File

@ -0,0 +1,42 @@
<div class="modal-header">
<h4>Case classification</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_case_classification" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="name" class="mr-4">Classification name
</label>
{{ form.name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="name_expanded" class="placeholder">Classification expanded name</label>
{{ form.name_expanded(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="description" class="placeholder">Classification description</label>
{{ form.description(class='form-control', autocomplete="off") }}
</div>
{% if case_classification.id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_case_classification('{{ case_classification.id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_case_classification">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_case_classification">Save</button>
{% endif %}
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,421 @@
{% set attributes = data.custom_attributes %}
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col align-self-center">
<h4 class="modal-title mr-4">{{ data.case_name|unquote }}
{% if data.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 data.modification_history %}<code>{{ mod|format_datetime('%Y-%m-%d %H:%M') }}</code> - {{ data.modification_history[mod].action }} by {{ data.modification_history[mod].user }}<br/>{% endfor %}</small>">
</i>
{% endif %}
</h4>
</div>
<div class="row text-center">
<ul class="nav nav-pills nav-default mr-4" id="pills-tab-custom-attr" role="tablist">
<li class="nav-item">
<a class="nav-link active show" id="pills-home-tab-nobd" data-toggle="pill" href="#details" role="tab" aria-controls="pills-home-nobd" aria-selected="false">Info</a>
</li>
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#case-info-access" role="tab" aria-controls="case-info-access" aria-selected="false">Access</a>
</li>
{% if attributes and attributes|length > 0 %}
{% for ca in attributes %}
<li class="nav-item submenu">
<a class="nav-link" data-toggle="pill" href="#{{page_uid}}{{ loop.index }}_{{ ca.lower() | replace(' ', '_' ) }}" role="tab" aria-controls="{{page_uid}}{{ loop.index }}_{{ ca.lower() | replace(' ', '_' ) }}" aria-selected="false">{{ca}}</a>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
<div class="col ">
<div class="row float-right">
<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 class="container col-md-12">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active" id="details">
<div id="case_gen_info_content">
<h4>General info <button class="ml-2 btn btn-sm float-right" onclick="edit_case_info();">Edit</button></h4>
<div class="row">
<div class="col-6 col-xs-12 col-md-6">
<div class="form-group">
<label>Case name :</label>
<span type="text" class="">{{ data.case_name|unquote }}</span>
</div>
<div class="form-group mt--3">
<label>Case description :</label>
<span type="text" class="">{{ data.case_description[0:50] }}</span>
</div>
<div class="form-group mt--3">
<label>Customer :</label>
<span type="text" class="text-faded">{{ data.customer_name }}</span>
</div>
<div class="form-group mt--3">
<label>Case tags :</label>
<span type="text" class="text-faded">{% if data.case_tags %} {% for tag in data.case_tags.split(',') %}
<span class="badge badge-pill badge-light ml-1"><i class="fa fa-tag mr-1"></i>{{ tag }}</span> {% endfor %}{% endif %}</span>
</div>
<div class="form-group mt--3">
<label>SOC ID :</label>
<span type="text" class="text-faded">{{ data.case_soc_id }}</span>
</div>
<div class="form-group mt--3">
<label>Case ID :</label>
<span type="text" class="text-faded">{{ data.case_id }}</span>
</div>
<div class="form-group mt--3">
<label>Case UUID :</label>
<span type="text" class="text-faded">{{ data.case_uuid }}</span>
</div>
</div>
<div class="col-6 col-xs-12 col-md-6">
<div class="form-group mt--3">
<label>Classification :</label>
{% if data.classification %}
<span type="text" class="text-faded">{{ data.classification }}</span>
{% else %}
<span type="text" class="text-faded">Unknown</span>
{% endif %}
</div>
<div class="form-group mt--3">
<label>State :</label>
<span type="text" class="text-faded">{{ data.state_name }}</span>
</div>
<div class="form-group mt--3">
<label>Open date :</label>
<span type="text" class="">{{ data.open_date }}</span>
</div>
<div class="form-group mt--3">
<label>Opening user :</label>
<span type="text" class="">{{ data.open_by_user }}</span>
</div>
<div class="form-group mt--3">
<label>Owner :</label>
<span type="text" class="">{{ data.owner }}</span>
</div>
{% if data.reviewer %}
<div class="form-group mt--3">
<label>Reviewer :</label>
<span type="text" class="">{{ data.reviewer }}</span>
</div>
{% endif %}
{% if data.review_status %}
<div class="form-group mt--3">
<label>Reviewer :</label>
<span type="text" class="">{{ data.review_status.status_name }}</span>
</div>
{% endif %}
{% if data.close_date %}
<div class="form-group mt--3">
<label>Close date :</label>
<span type="text" class="">{{ data.close_date }}</span>
</div>
{% endif %}
{% if protagonists %}
{% for protagonist in protagonists %}
<div class="form-group mt--3">
<label>{{ protagonist.role }}: </label>
<span type="text" class="">{{ protagonist.name }} ({{protagonist.contact}})</span>
</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
<div id="case_gen_info_edit" style="display:none;">
<form method="post" action='' id="form_update_case" autocomplete="off">
<h4 class=" pb-3">Edit case information</h4>
<div class="col-12">
{{ form.hidden_tag() }}
<div class="mt-4">
<div class="row">
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">Case name <i class="ml-2">{{"#{} - ".format(data.case_id)}}</i></span>
</div>
<input type="text" class="form-control" name="case_name" value="{{ data.case_name.replace('#{} - '.format(data.case_id), '')}}">
</div>
</div>
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text">SOC ticket ID</span>
</div>
<input type="text" class="form-control" name="case_soc_id" value="{{ data.case_soc_id }}">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">Classification</span>
</div>
<select class="selectpicker form-control"
id="case_quick_classification" name="classification_id">
{% for clc in case_classifications %}
<option value="{{ clc.id }}" {% if data.classification_id == clc.id %}selected{% endif %} class="badge-text">{{ clc.name_expanded }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">Owner</span>
</div>
<select class="selectpicker form-control"
id="case_quick_owner" name="owner_id">
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">State</span>
</div>
<select class="selectpicker form-control"
id="case_state" name="state_id">
{% for clc in case_states %}
<option value="{{ clc.state_id }}" {% if data.state_id == clc.state_id %}selected{% endif %} class="badge-text">{{ clc.state_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">Outcome</span>
</div>
<select class="form-control selectpicker"
id="case_quick_status" name="status_id">
<option value="0" {% if data.status_id == 0 %}selected{% endif %} class="badge-text">Unknown</option>
<option value="1" {% if data.status_id == 1 %}selected{% endif %} class="badge-text">False Positive</option>
<option value="2" {% if data.status_id == 2 %}selected{% endif %} class="badge-text">True Positive with impact</option>
<option value="4" {% if data.status_id == 4 %}selected{% endif %} class="badge-text">True Positive without impact</option>
<option value="3" {% if data.status_id == 3 %}selected{% endif %} class="badge-text">Not applicable</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-6">
<div class="input-group mb-2">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">Customer</span>
</div>
<select class="selectpicker form-control"
id="case_quick_customer" name="case_customer">
{% for cst in customers %}
<option value="{{ cst.customer_id }}" {% if data.customer_id == cst.customer_id %}selected{% endif %} class="badge-text">{{ cst.customer_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-xs-12 col-6">
<div class="input-group mb-4">
<div class="input-group-prepend fix-label-item">
<span class="input-group-text">Reviewer</span>
</div>
<select class="selectpicker form-control"
id="case_quick_reviewer" name="reviewer_id">
</select>
</div>
</div>
</div>
<div class="form-group px-0">
<label for="asset_tags">Case tags
</label>
<input type="text" id="case_tags"
class="form-control col-md-12" {% if data.case_tags %} value="{{ data.case_tags }}" {% endif %}/>
</div>
<div class="form-group px-4 py-4 mt-3" id="protagonists_form_group">
{% if protagonists %}
<label>Protagonists
</label>
{% for iprota in protagonists %}
<div class="input-group mb-2 mt-2" id="protagonist_{{loop.index}}">
<div class="col-6">
<h6># {{loop.index}}</h6>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingrole_{{loop.index}}">Role</span>
</div>
<input type="text" aria-describedby="ingrole_{{loop.index}}" list="roles-list" class="form-control" name="protagonist_role_{{loop.index}}" value="{{ iprota.role }}" placeholder="Role">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingname_{{loop.index}}">Name</span>
</div>
<input type="text" aria-describedby="ingname_{{loop.index}}" class="form-control" name="protagonist_name_{{loop.index}}" placeholder="Name" list="username-list" value="{{ iprota.name }}">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingp_{{loop.index}}">Contact</span>
</div>
<input type="text" aria-describedby="ingp_{{loop.index}}" class="form-control" name="protagonist_contact_{{loop.index}}" value="{{ iprota.contact }}" placeholder="Contact" list="emails-list">
</div>
<button type="button" class="btn btn-sm btn-outline-dark mr-2 mt-1" onclick="remove_protagonist('{{loop.index}}');">
Remove
</button>
<hr/>
</div>
</div>
{% endfor %}
{% endif %}
<div id="protagonist_list_edit">
</div>
<button type="button" class="btn btn-sm btn-outline-dark mt-2 mr-2" id="add_protagonist_btn" onclick="add_protagonist();">Add protagonist</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane" id="case-info-access">
<div class="row">
<div class="col">
<div class="card-title">Case access</div>
</div>
<div class="col">
<button class="btn btn-dark btn-sm ml-3 float-right" onclick="access_case_info_reload('{{ data.case_id }}');">Refresh</button>
<button class="btn btn-dark btn-sm ml-2 float-right" onclick="view_case_access_via_group('{{ data.case_id }}');">Set access via group</button>
</div>
</div>
<div class="row mt-4">
<div class="col">
<table class="table display table-striped table-hover responsive" width="100%" cellspacing="0" id="case_access_users_list_table" >
<thead>
<tr>
<th>User ID</th>
<th>User Name</th>
<th>User Login</th>
<th>User Access</th>
</tr>
</thead>
<tfoot>
<tr>
<th>User ID</th>
<th>User Name</th>
<th>User Login</th>
<th>User Access</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
</div>
</div>
</div>
<div id="protagonist_list_edit_template" style="display:none;">
<div class="input-group mb-2 mt-2" id="protagonist___PROTAGONIST_ID__">
<div class="col-6">
<h5>Protagonist</h5>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingrole___PROTAGONIST_ID__">Role</span>
</div>
<input type="text" aria-describedby="ingrole___PROTAGONIST_ID__" list="roles-list" class="form-control" name="protagonist_role___PROTAGONIST_ID__" value="" placeholder="Role">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingname___PROTAGONIST_ID__">Name</span>
</div>
<input type="text" aria-describedby="ingname___PROTAGONIST_ID__" class="form-control" name="protagonist_name___PROTAGONIST_ID__" placeholder="Name" list="username-list" value="">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="ingp___PROTAGONIST_ID__">Contact</span>
</div>
<input type="text" aria-describedby="ingp___PROTAGONIST_ID__" class="form-control" name="protagonist_contact___PROTAGONIST_ID__" value="" list="emails-list" placeholder="Contact">
</div>
<button type="button" class="btn btn-sm btn-outline-dark mr-2 mt-1" onclick="remove_protagonist('__PROTAGONIST_ID__');">
Remove
</button>
<hr/>
</div>
</div>
</div>
<datalist id="roles-list">
<option value="Analyst">
<option value="Lead">
<option value="Communication">
<option value="Customer contact">
</datalist>
<datalist id="username-list">
</datalist>
<datalist id="emails-list">
</datalist>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger " onclick="remove_case('{{ data.case_id }}');"
id="delete_case_info">Delete case</button>
{% if not data.close_date %}
<button type="button" class="btn btn-outline-warning mr-auto" onclick="close_case('{{ data.case_id }}');"
id="close_case_info">Close case</button>
{% else %}
<button type="button" class="btn btn-success" onclick="reopen_case('{{ data.case_id }}');"
id="reopen_case_info">Reopen case</button>
{% endif %}
<button type="button" class="btn btn-outline-dark mr-2" style="display:none;" id="cancel_case_info" onclick="cancel_case_edit();">Cancel</button>
<button type="button" class="btn btn-outline-success mr-2" style="display:none;" id="save_case_info" onclick="save_case_edit({{ data.case_id }});">Save</button>
</div>
<script>
$('[data-toggle="popover"]').popover();
$('#case_quick_classification').selectpicker({
liveSearch: true,
title: "Classification",
style: "btn-outline-white"
});
$('#case_quick_owner').selectpicker({
liveSearch: true,
title: "Owner",
style: "btn-outline-white"
});
$('#case_quick_reviewer').selectpicker({
liveSearch: true,
title: "Reviewer",
style: "btn-outline-white"
});
$('#case_state').selectpicker({
liveSearch: true,
title: "Case state",
style: "btn-outline-white"
});
$('#case_quick_status').selectpicker({
liveSearch: true,
title: "Outcome",
style: "btn-outline-white"
});
$('#case_quick_customer').selectpicker({
liveSearch: true,
title: "Customer",
style: "btn-outline-white"
});
access_case_info_reload('{{ data.case_id }}', '{{ data.owner_id }}', '{{ data.reviewer_id }}');
</script>

View File

@ -0,0 +1,42 @@
<div class="modal-header">
<h4>Case state</h4>
<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="container col-md-12">
<form method="post" action="" id="form_new_case_state" enctype="multipart/form-data">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<label for="name" class="mr-4">State name
</label>
{{ form.state_name(class='form-control', autocomplete="off") }}
</div>
<div class="form-group mt-3">
<label for="description" class="placeholder">State description</label>
{{ form.state_description(class='form-control', autocomplete="off") }}
</div>
{% if not case_state.protected %}
{% if case_state.state_id %}
<button type="button" class="btn btn-outline-danger mt-5"
onclick="delete_case_state('{{ case_state.state_id }}');">Delete</button>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_case_state">Update</button>
{% else %}
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_case_state">Save</button>
{% endif %}
{% else %}
<i class="fa fa-lock mt-4 mr-2 ml-3 text-danger mb-4" aria-hidden="true"></i> This state is protected and cannot be deleted or updated.
{% endif %}
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,177 @@
<div class="modal-header">
{% if case_template.id %}
<h4>Edit case template {{ case_template.display_name }}</h4>
{% else %}
<h4>Add case template</h4>
{% endif %}
<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="container col-md-12">
<form method="post" action="" id="form_new_case_template" enctype="multipart/form-data">
<div class="row">
<div class="col-6">
<div class="accordion accordion-primary">
{% if case_template.id %}
<h2>{{ case_template.display_name }} template</h2>
{% else %}
<h2>New template</h2>
{% endif %}
<p>Case templates allow to prefill case objects such as tasks, tags, and notes.<br/>
It can be used to add procedures defining how to react against a specific kind of incident (phishing, ransomware, APT...)</p>
<div class="alert-std alert-warning" role="alert">
Case templates can be added and edited in this UI, or they can be uploaded as JSON files.<br/>
<p>IRIS will attempt to validate the contents of the case template before committing.</p>
</div>
<div class="card">
<div class="card-header collapsed" id="drop_case_template_taxonomy" data-toggle="collapse" data-target="#drop_tax_case_template" aria-expanded="false" aria-controls="drop_tax_attr" role="button">
<div class="span-icon">
<div class="flaticon-pencil"></div>
</div>
<div class="span-title">
Case Template taxonomy
</div>
<div class="span-mode"></div>
</div>
<div id="drop_tax_case_template" class="collapse" aria-labelledby="drop_tax_case_template" style="">
<div class="card-body">
<h4>Field types</h4>
The supported fields types are:
<ul>
<li>name: The name of the case template (required).</li>
<li>display_name: The displayed name of the case template.</li>
<li>description: The description of the case template.</li>
<li>author: The author of the case template (not related to the current user).</li>
<li>classification: The classification of the case template. Should be a lowercase name matching an existing classification in IRIS.</li>
<li>title_prefix: A prefix to add to case title.</li>
<li>summary: content to prefill the summary.</li>
<li>tags: A list of case tags.</li>
<li>tasks: A list of dictionaries defining tasks. Tasks are defined by title (required), description, and list of tags.</li>
<li>note_groups: A list of dictionaries defining note groups. Note groups are defined by title (required), and list of notes. Notes have title (required) and content</li>
</ul>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header collapsed" id="drop__template_example" data-toggle="collapse" data-target="#drop_case_template_example" aria-expanded="false" aria-controls="drop_tax_attr" role="button">
<div class="span-icon">
<div class="flaticon-pencil"></div>
</div>
<div class="span-title">
Case Template Example
</div>
<div class="span-mode"></div>
</div>
<div id="drop_case_template_example" class="collapse" aria-labelledby="drop_tax_case_template" style="">
<div class="card-body">
A case template is defined as below.
<pre>
{
"name": "ransomware_infection",
"display_name": "Ransomware Infection Template",
"description": "This case template describes first-response tasks to handle information system compromised by a ransomware.",
"author": "DFIR-IRIS",
"classification": "malicious-code:ransomware",
"title_prefix": "[RANS]",
"summary": "# Context \n\n\n# Contact \n\n\n# Actions \n\n\n",
"tags": ["ransomware","malware"],
"tasks": [
{
"title": "Identify the perimeter",
"description": "The perimeter of compromise must be identified",
"tags": ["identify"]
},
{
"title": "Collect compromised hosts",
"description": "Deploy Velociraptor and start collecting evidence",
"tags": ["collect", "velociraptor"]
},
{
"title": "Containment"
}
],
"note_groups": [
{
"title": "Identify",
"notes": [
{
"title": "Identify the compromised accounts",
"content": "# Observations\n\n"
}
]
},
{
"title": "Collect",
"notes": [
{
"title": "Velociraptor deployment"
},
{
"title": "Assets collected",
"content": "# Assets collected\n\n# Assets not collected"
}
]
}
]
}
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="col-6">
{{ form.hidden_tag() }}
<div class="form-group">
<div class="row">
<label for="editor_detail" class="mr-4">Case template definition
</label><button type="button" class="btn btn-sm ml-auto" onclick="downloadCaseTemplateDefinition();">Download definition</button>
</div>
<div id="editor_detail">{{ form.case_template_json.data|tojsonsafe }}</div>
</div>
</div>
</div>
</form>
</div>
<div class="alert alert-warning" style="display:none;" role="alert" id="alert_case_template_details">
<span id="alert_case_template_edit"></span><br/>
<b>Logs:</b>
<ul id="case_template_err_details_list">
</ul>
</div>
</div>
<div class="modal-footer">
{% if case_template.id %}
<button
type="button"
class="btn btn-outline-danger mr-auto"
id="submit_delete_case_template"
>
Delete
</button>
<button
type="button"
class="btn btn-outline-success ml-4 float-right"
id="submit_new_case_template"
>
Update
</button>
{% else %}
<button
type="button"
class="btn btn-outline-success ml-4 float-right"
id="submit_new_case_template"
>
Save
</button>
{% endif %}
</div>

View File

@ -0,0 +1,101 @@
<div class="modal-header">
<div class="col md-12">
<div class="row">
<div class="col">
<h4 class="modal-title mt-2 mr-4">{% if contact.id %}Edit contact #{{ contact.id }}{% else %}Add contact{% endif %}</h4>
<small><a class="text-muted">{% if contact.contact_uuid %}#{{ contact.contact_uuid }}{% endif %}</a></small>
</div>
<div class="col ">
<div class="row float-right">
<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_contact" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<div class="form-group row">
<label for="contact_name" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Contact name *</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_name(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group row">
<label for="contact_role" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Contact Role</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_role(class='form-control', autocomplete="off", list="contact_roles_list") }}
</div>
<datalist id="contact_roles_list">
<option value="CISO">
<option value="CEO">
<option value="Manager">
<option value="Sales">
<option value="Support">
<option value="Billing">
<option value="Other">
</datalist>
</div>
<div class="form-group row">
<label for="email" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Contact email</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_email(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group row">
<label for="work_phone" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Contact work phone</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_work_phone(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group row">
<label for="mobile_phone" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Contact mobile phone</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_mobile_phone(class='form-control', autocomplete="off") }}
</div>
</div>
<div class="form-group row">
<label for="notes" class="col-lg-3 col-md-3 col-sm-4 mt-sm-2 text-right">Additional notes</label>
<div class="col-lg-8 col-md-9 col-sm-8">
{{ form.contact_note(class='form-control', autocomplete="off") }}
</div>
</div>
</form>
</div>
</div>
</div>
{% if contact.id %}
<button
type="button"
class="btn btn-outline-danger mt-5"
id="submit_delete_contact"
>
Delete
</button>
<button
type="button"
class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_contact"
>
Update
</button>
{% else %}
<button
type="button"
class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_new_contact"
>
Save
</button>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,130 @@
<div class="modal-header">
<h4>{{ data.module_human_name|unquote }} - Module Information</h4>
<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="container col-6">
<div class="form-group">
<label>Name :</label>
<span type="text" class="">{{ data.module_human_name|unquote }}</span>
</div>
<div class="form-group mt--3">
<label>Description :</label>
<span type="text" class="">{{ data.module_description }}</span>
</div>
<div class="form-group mt--3">
<label>Target package :</label>
<span type="text" class="text-faded">{{ data.module_name }}</span>
</div>
<div class="form-group mt--3">
<label>Date added :</label>
<span type="text" class="text-faded">{{ data.date_added }}</span>
</div>
{% if not is_configured %}
<div class="form-group">
<b class="text-danger">The module was automatically disabled because mandatory parameters are not set.</b>
</div>
{% endif %}
</div>
<div class="container col-6">
<div class="form-group">
<label>Module version :</label>
<span type="text" class="text-faded">{{ data.module_version }}</span>
</div>
<div class="form-group mt--3">
<label>Interface version :</label>
<span type="text" class="text-faded">{{ data.interface_version }}</span>
</div>
<div class="form-group mt--3">
<label>Is active :</label>
<span type="text" class="text-faded">{{ data.is_active }} </span>
</div>
<div class="form-group mt--3">
<label>Provides pipeline :</label>
<span type="text" class="text-faded">{{ data.has_pipeline }}</span>
</div>
</div>
</div>
<div class="container col-12">
<hr/>
<div class="form-group mt-3">
<label>Configuration (click on a setting to edit)</label>
<button type="button" class="btn btn-sm btn-outline-dark float-right" onclick="export_mod_config('{{ data.id }}');">Export configuration</button>
<button type="button" class="btn btn-sm btn-outline-dark float-right mr-2" data-toggle="modal" data-target="#modal_input_config">Import configuration</button>
</div>
<div class="form-group mt-3">
<table id="datatable_db_list" class="table display table-bordered table-striped table-hover mt-4" width="100%"
cellspacing="0">
<thead>
<tr role="row">
<th>Section</th>
<th>Parameter</th>
<th>Value</th>
<th>Mandatory</th>
</tr>
</thead>
<tbody>
{% for id in config %}
<tr role="row">
<td>{{ id["section"] or "Main" }}</td>
<td><a href="#" onclick="update_param({{ data.id }}, '{{ data.id }}##{{ id['param_name'] }}');">
{% if (id['param_name'] in missing_params) %}
<i class="fa-solid fa-triangle-exclamation text-danger" title="Mandatory parameter not set"></i>
{% endif %}
{{ id["param_human_name"] }}
</a></td>
<td>{% if id["value"] is not none %}{% if id["type"] != "sensitive_string" %} {% if id["value"] is string %}{{ id["value"][0:20] }}{% else %}{{id["value"]}}{% endif %} {% else %} *************** {% endif %}{% endif %}</td>
<td>{{ id["mandatory"] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger mr-auto" onclick="remove_module('{{ data.id }}');">Remove module</button>
{% if is_configured and not data.is_active %}
<button type="button" class="btn btn-outline-success" onclick="enable_module('{{ data.id }}');">Enable module</button>
{% elif is_configured and data.is_active %}
<button type="button" class="btn btn-outline-warning" onclick="disable_module('{{ data.id }}');">Disable module</button>
{% endif %}
<button type="button" class="btn btn-default" data-dismiss="modal">Dismiss</button>
</div>
<div class="modal bg-shadow-gradient" tabindex="-1" role="dialog" id="modal_input_config" data-backdrop="true">
<div class="modal-lg modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5>Import module configuration from file</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">
<p>Select a configuration file to upload. Existing configuration for this module will be overwritten.</p>
<label class="placeholder">Configuration file : </label>
<input id="input_configuration_file" type="file" accept="text/json">
<button type="button" class="btn btn-outline-success float-right mr-2" onclick="import_mod_config('{{ data.id }}');">Import</button>
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
<script>
$(document).ready(function(){
$("#datatable_db_list").DataTable({
filter: true,
order: [[0, 'asc']],
rowGroup: {
dataSrc: 0
}
});
});
</script>

View File

@ -0,0 +1,22 @@
<div class="modal-header">
<h4 class="modal-title mt-1">Attribute preview
</h4>
{% include 'modals/modal_attributes_nav.html' %}
<div class="row text-right">
<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>
<div class="modal-body">
<div role="tabpanel">
<div class="tab-content">
<div class="tab-pane active text-center" id="details">
<h2>Base object information</h2>
<b>This is for preview purpose only. Standard object information will be rendered here.</b><br/>
Don't mind the background, it's for preview also
</div>
{% include 'modals/modal_attributes_tabs.html' %}
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
<div class="modal-header">
<h4 class="modal-title mt-2 mr-4">Server Updates</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="col-md-12" id="updates_content_md">
{{ updates_content }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Dismiss</button>
{% if has_updates %}
<a type="button" class="btn btn-success" href="/manage/server/make-update?cid={{session['current_case'].case_id}}">Update server</a>
{% endif %}
</div>

View File

@ -0,0 +1,74 @@
<div class="modal-header">
<h4>Update {{ parameter["param_human_name"] }} of {{ mod_name }} </h4>
<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="container col-md-12">
<form method="post" action="" id="form_update_param">
<div class="col-md-12 col-lg-12 col-sm-12">
{{ form.hidden_tag() }}
<div class="form-group">
<b>Description :</b> {{ parameter["param_description"] }}<br/>
<b>Default :</b> {{ parameter["default"] }} <br/>
<b>Mandatory :</b> {{ parameter["mandatory"] }}<br/>
<b>Expected type :</b> {{ parameter["type"] }}<br/>
</div>
<div class="form-group mt-3">
{% if parameter["type"].startswith("textfield_") %}
<label class="placeholder"><b>Value</b></label>
<div id="editor_detail">{{ parameter["value"] }}</div>
{% elif parameter["type"] == 'bool' %}
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" id="parameter_value"
class="form-control"
{% if parameter["value"] == True %} checked {% endif %}
/>
<span class="form-check-sign">Check to set to true</span>
</label>
</div>
{% else %}
<label class="placeholder"><b>Value</b></label>
<input class="form-control required" name="parameter_value" type="text" value="{{ parameter["value"] }}">
{% endif %}
</div>
<button type="button" class="btn btn-outline-success ml-4 mt-5 float-right"
id="submit_save_parameter">Save</button>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function(){
if ($('#editor_detail').length != 0) {
var editor = ace.edit("editor_detail",
{
autoScrollEditorIntoView: true,
minLines: 30,
});
editor.setTheme("ace/theme/tomorrow");
{% if parameter["type"].endswith("json") %}
editor.session.setMode("ace/mode/json");
{% elif parameter["type"].endswith("html") %}
editor.session.setMode("ace/mode/html");
{% elif parameter["type"].endswith("markdown") %}
editor.session.setMode("ace/mode/markdown");
{% endif %}
editor.renderer.setShowGutter(true);
editor.setOption("showLineNumbers", true);
editor.setOption("showPrintMargin", false);
editor.setOption("displayIndentGuides", true);
editor.setOption("maxLines", "Infinity");
editor.session.setUseWrapMode(true);
editor.setOption("indentedSoftWrap", true);
editor.renderer.setScrollMargin(8, 5);
}
});
</script>

View File

@ -0,0 +1,121 @@
<div class="modal-header">
<h4>Upload case template</h4>
<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="container col-md-12">
<form method="post" action="" id="form_upload_case_template">
<div class="modal-content">
<div class="modal-body">
<div class="accordion accordion-primary">
<p>Case templates allow to prefill case objects such as tasks, tags, and notes.<br/>
It could be used to add procedures defining how to react against a specific kind of incident (phishing, ransomware, APT...)</p>
<div class="alert-std alert-warning" role="alert">
Case templates can be uploaded as JSON files, or they can be added and edited in the Add/Edit case template UI.<br/>
</div>
<label class="placeholder">JSON File format example</label>
<div class="card">
<div class="card-header collapsed" id="drop_up_case_template_taxonomy" data-toggle="collapse" data-target="#drop_up_tax_case_template" aria-expanded="false" aria-controls="drop_up_tax_attr" role="button">
<div class="span-icon">
<div class="flaticon-pencil"></div>
</div>
<div class="span-title">
Case Template taxonomy
</div>
<div class="span-mode"></div>
</div>
<div id="drop_up_tax_case_template" class="collapse" aria-labelledby="drop_up_tax_case_template" style="">
<div class="card-body">
A case template is defined as below.
<pre>
{
"name": "ransomware_infection",
"display_name": "Ransomware Infection Template",
"description": "This case template describes first-response tasks to handle information system compromised by a ransomware.",
"author": "DFIR-IRIS",
"title_prefix": "RANS",
"summary": "# Context \n\n\n # Contact \n\n\n # Actions \n\n\n",
"tags": ["ransomware","malware"],
"tasks": [
{
"title": "Identify the perimeter",
"description": "The perimeter of compromise must be identified",
"tags": ["identify"]
},
{
"title": "Collect compromised hosts",
"description": "Deploy Velociraptor and start collecting evidence",
"tags": ["collect", "velociraptor"]
},
{
"title": "Contain"
}
],
"note_groups": [
{
"title": "Identify",
"notes": [
{
"title": "Identify the compromised accounts",
"content": "# Observations\n\n"
}
]
},
{
"title": "Collect",
"notes": [
{
"title": "Velociraptor deployment"
},
{
"title": "Assets collected",
"content": "# Assets collected\n\n# Assets not collected"
}
]
}
]
}
</pre>
<h4>Field types</h4>
The supported fields types are:
<ul>
<li>name: The name of the case template (required).</li>
<li>display_name: The displayed name of the case template.</li>
<li>description: The description of the case template.</li>
<li>author: The author of the case template (not related to the current user).</li>
<li>title_prefix: A prefix to add to case title.</li>
<li>summary: content to prefill the summary.</li>
<li>tags: A list of case tags.</li>
<li>tasks: A list of dictionaries defining tasks. Tasks are defined by title (required), description, and list of tags.</li>
<li>note_groups: A list of dictionaries defining note groups. Note groups are defined by title (required), and list of notes. Notes have title (required) and content</li>
</ul>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="placeholder">Choose JSON file to import : </label>
<input id="input_upload_case_template" type="file" accept="text/json">
</div>
</div>
<div class='invalid-feedback' id='ctempl-invalid-msg'></div>
</div><!-- /.modal-content -->
</form>
</div>
<div class="alert alert-warning" style="display:none;" role="alert" id="alert_upload_case_template_details">
<span id="alert_upload_case_template"></span><br/>
<b>Logs:</b>
<ul id="upload_case_template_err_details_list">
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-success" onclick="upload_case_template();">Upload</button>
</div>