This commit is contained in:
0
iris-web/source/app/blueprints/manage/__init__.py
Normal file
0
iris-web/source/app/blueprints/manage/__init__.py
Normal 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)
|
||||
|
||||
|
@ -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))
|
@ -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
|
@ -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))
|
||||
|
@ -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")
|
@ -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))
|
231
iris-web/source/app/blueprints/manage/manage_case_state.py
Normal file
231
iris-web/source/app/blueprints/manage/manage_case_state.py
Normal 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")
|
@ -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")
|
568
iris-web/source/app/blueprints/manage/manage_cases_routes.py
Normal file
568
iris-web/source/app/blueprints/manage/manage_cases_routes.py
Normal 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)
|
404
iris-web/source/app/blueprints/manage/manage_customers_routes.py
Normal file
404
iris-web/source/app/blueprints/manage/manage_customers_routes.py
Normal 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")
|
@ -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))
|
||||
|
383
iris-web/source/app/blueprints/manage/manage_groups.py
Normal file
383
iris-web/source/app/blueprints/manage/manage_groups.py
Normal 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)
|
199
iris-web/source/app/blueprints/manage/manage_ioc_types_routes.py
Normal file
199
iris-web/source/app/blueprints/manage/manage_ioc_types_routes.py
Normal 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))
|
321
iris-web/source/app/blueprints/manage/manage_modules_routes.py
Normal file
321
iris-web/source/app/blueprints/manage/manage_modules_routes.py
Normal 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)
|
@ -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)
|
||||
|
@ -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))
|
@ -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)
|
@ -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())
|
213
iris-web/source/app/blueprints/manage/manage_templates_routes.py
Normal file
213
iris-web/source/app/blueprints/manage/manage_templates_routes.py
Normal 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)
|
51
iris-web/source/app/blueprints/manage/manage_tlps_routes.py
Normal file
51
iris-web/source/app/blueprints/manage/manage_tlps_routes.py
Normal 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)
|
||||
|
||||
|
489
iris-web/source/app/blueprints/manage/manage_users.py
Normal file
489
iris-web/source/app/blueprints/manage/manage_users.py
Normal 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)
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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">×</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 -->
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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">×</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>
|
@ -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">×</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>
|
@ -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>
|
@ -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">×</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>
|
@ -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">×</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>
|
@ -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">×</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>
|
@ -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">×</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>
|
@ -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>
|
@ -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">×</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>
|
@ -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">×</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>
|
||||
|
@ -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>
|
@ -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">×</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">×</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>
|
@ -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>
|
@ -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>
|
@ -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">×</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>
|
@ -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">×</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>
|
||||
|
Reference in New Issue
Block a user