hcornet 506716e703
Some checks failed
Deployment Verification / deploy-and-test (push) Failing after 29s
first sync
2025-03-04 07:59:21 +01:00

1003 lines
32 KiB
Python

#!/usr/bin/env python3
#
# IRIS Source Code
# Copyright (C) 2023 - DFIR-IRIS
# 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 json
import marshmallow
from datetime import datetime
from flask import Blueprint, request, render_template, redirect, url_for
from flask_login import current_user
from flask_wtf import FlaskForm
from typing import Union, List
from werkzeug import Response
import app
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.alerts.alerts_db import get_filtered_alerts, get_alert_by_id, create_case_from_alert, \
merge_alert_in_case, unmerge_alert_from_case, cache_similar_alert, get_related_alerts, get_related_alerts_details, \
get_alert_comments, delete_alert_comment, get_alert_comment, delete_similar_alert_cache, delete_alerts, \
create_case_from_alerts
from app.datamgmt.case.case_db import get_case
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.utils.tracker import track_activity
from app.models.alerts import AlertStatus
from app.models.authorization import Permissions
from app.schema.marshables import AlertSchema, CaseSchema, CommentSchema, CaseAssetsSchema, IocSchema
from app.util import ac_api_requires, response_error, add_obj_history_entry, ac_requires
from app.util import response_success
alerts_blueprint = Blueprint(
'alerts',
__name__,
template_folder='templates'
)
# CONTENT ------------------------------------------------
@alerts_blueprint.route('/alerts/filter', methods=['GET'])
@ac_api_requires(Permissions.alerts_read, no_cid_required=True)
def alerts_list_route(caseid) -> Response:
"""
Get a list of alerts from the database
args:
caseid (str): The case id
returns:
Response: The response
"""
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
alert_ids_str = request.args.get('alert_ids')
alert_ids = None
if alert_ids_str:
try:
if ',' in alert_ids_str:
alert_ids = [int(alert_id) for alert_id in alert_ids_str.split(',')]
else:
alert_ids = [int(alert_ids_str)]
except ValueError:
return response_error('Invalid alert id')
alert_assets_str = request.args.get('alert_assets')
alert_assets = None
if alert_assets_str:
try:
if ',' in alert_assets_str:
alert_assets = [str(alert_asset) for alert_asset in alert_assets_str.split(',')]
else:
alert_assets = [str(alert_assets_str)]
except ValueError:
return response_error('Invalid alert asset')
alert_iocs_str = request.args.get('alert_iocs')
alert_iocs = None
if alert_iocs_str:
try:
if ',' in alert_iocs_str:
alert_iocs = [str(alert_ioc) for alert_ioc in alert_iocs_str.split(',')]
else:
alert_iocs = [str(alert_iocs_str)]
except ValueError:
return response_error('Invalid alert ioc')
alert_schema = AlertSchema()
filtered_data = get_filtered_alerts(
start_date=request.args.get('source_start_date'),
end_date=request.args.get('source_end_date'),
title=request.args.get('alert_title'),
description=request.args.get('alert_description'),
status=request.args.get('alert_status_id', type=int),
severity=request.args.get('alert_severity_id', type=int),
owner=request.args.get('alert_owner_id', type=int),
source=request.args.get('alert_source'),
tags=request.args.get('alert_tags'),
classification=request.args.get('alert_classification_id', type=int),
client=request.args.get('alert_customer_id'),
case_id=request.args.get('case_id', type=int),
alert_ids=alert_ids,
page=page,
per_page=per_page,
sort=request.args.get('sort'),
assets=alert_assets,
iocs=alert_iocs,
resolution_status=request.args.get('alert_resolution_id', type=int)
)
if filtered_data is None:
return response_error('Filtering error')
alerts = {
'total': filtered_data.total,
'alerts': alert_schema.dump(filtered_data.items, many=True),
'last_page': filtered_data.pages,
'current_page': filtered_data.page,
'next_page': filtered_data.next_num if filtered_data.has_next else None,
}
return response_success(data=alerts)
@alerts_blueprint.route('/alerts/add', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_add_route(caseid) -> Response:
"""
Add a new alert to the database
args:
caseid (str): The case id
returns:
Response: The response
"""
if not request.json:
return response_error('No JSON data provided')
alert_schema = AlertSchema()
ioc_schema = IocSchema()
asset_schema = CaseAssetsSchema()
try:
# Load the JSON data from the request
data = request.get_json()
iocs_list = data.pop('alert_iocs', [])
assets_list = data.pop('alert_assets', [])
iocs = ioc_schema.load(iocs_list, many=True)
assets = asset_schema.load(assets_list, many=True)
# Deserialize the JSON data into an Alert object
new_alert = alert_schema.load(data)
new_alert.alert_creation_time = datetime.utcnow()
new_alert.iocs = iocs
new_alert.assets = assets
# Add the new alert to the session and commit it
db.session.add(new_alert)
db.session.commit()
# Add history entry
add_obj_history_entry(new_alert, 'Alert created')
# Cache the alert for similarities check
cache_similar_alert(new_alert.alert_customer_id, assets=assets_list,
iocs=iocs_list, alert_id=new_alert.alert_id,
creation_date=new_alert.alert_source_event_time)
new_alert = call_modules_hook('on_postload_alert_create', data=new_alert, caseid=caseid)
track_activity(f"created alert #{new_alert.alert_id} - {new_alert.alert_title}", ctx_less=True)
# Emit a socket io event
app.socket_io.emit('new_alert', json.dumps({
'alert_id': new_alert.alert_id,
'alert_title': new_alert.alert_title
}), namespace='/alerts')
# Return the newly created alert as JSON
return response_success(data=alert_schema.dump(new_alert))
except Exception as e:
app.app.logger.exception(e)
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/<int:alert_id>', methods=['GET'])
@ac_api_requires(Permissions.alerts_read, no_cid_required=True)
def alerts_get_route(caseid, alert_id) -> Response:
"""
Get an alert from the database
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
alert_schema = AlertSchema()
# Get the alert from the database
alert = get_alert_by_id(alert_id)
# Return the alert as JSON
if alert is None:
return response_error('Alert not found')
alert_dump = alert_schema.dump(alert)
# Get similar alerts
similar_alerts = get_related_alerts(alert.alert_customer_id, alert.assets, alert.iocs)
alert_dump['related_alerts'] = similar_alerts
return response_success(data=alert_dump)
@alerts_blueprint.route('/alerts/similarities/<int:alert_id>', methods=['GET'])
@ac_api_requires(Permissions.alerts_read, no_cid_required=True)
def alerts_similarities_route(caseid, alert_id) -> Response:
"""
Get an alert and similarities from the database
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
# Get the alert from the database
alert = get_alert_by_id(alert_id)
# Return the alert as JSON
if alert is None:
return response_error('Alert not found')
open_alerts = request.args.get('open-alerts', 'false').lower() == 'true'
open_cases = request.args.get('open-cases', 'false').lower() == 'true'
closed_cases = request.args.get('closed-cases', 'false').lower() == 'true'
closed_alerts = request.args.get('closed-alerts', 'false').lower() == 'true'
days_back = request.args.get('days-back', 30, type=int)
number_of_results = request.args.get('number-of-nodes', 100, type=int)
if number_of_results < 0:
number_of_results = 100
if days_back < 0:
days_back = 30
# Get similar alerts
similar_alerts = get_related_alerts_details(alert.alert_customer_id, alert.assets, alert.iocs,
open_alerts=open_alerts, open_cases=open_cases,
closed_cases=closed_cases, closed_alerts=closed_alerts,
days_back=days_back, number_of_results=number_of_results)
return response_success(data=similar_alerts)
@alerts_blueprint.route('/alerts/update/<int:alert_id>', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_update_route(alert_id, caseid) -> Response:
"""
Update an alert in the database
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
if not request.json:
return response_error('No JSON data provided')
alert = get_alert_by_id(alert_id)
if not alert:
return response_error('Alert not found')
alert_schema = AlertSchema()
do_resolution_hook = False
do_status_hook = False
try:
# Load the JSON data from the request
data = request.get_json()
activity_data = []
for key, value in data.items():
old_value = getattr(alert, key, None)
if type(old_value) == int:
old_value = str(old_value)
if type(value) == int:
value = str(value)
if old_value != value:
if key == "alert_resolution_status_id":
do_resolution_hook = True
if key == 'alert_status_id':
do_status_hook = True
activity_data.append(f"\"{key}\" from \"{old_value}\" to \"{value}\"")
# Deserialize the JSON data into an Alert object
updated_alert = alert_schema.load(data, instance=alert, partial=True)
if data.get('alert_owner_id') is None and updated_alert.alert_owner_id is None:
updated_alert.alert_owner_id = current_user.id
# Save the changes
db.session.commit()
updated_alert = call_modules_hook('on_postload_alert_update', data=updated_alert, caseid=caseid)
if do_resolution_hook:
updated_alert = call_modules_hook('on_postload_alert_resolution_update', data=updated_alert, caseid=caseid)
if do_status_hook:
updated_alert = call_modules_hook('on_postload_alert_status_update', data=updated_alert, caseid=caseid)
if activity_data:
track_activity(f"updated alert #{alert_id}: {','.join(activity_data)}", ctx_less=True)
add_obj_history_entry(updated_alert, f"updated alert: {','.join(activity_data)}")
else:
track_activity(f"updated alert #{alert_id}", ctx_less=True)
add_obj_history_entry(updated_alert, f"updated alert")
db.session.commit()
# Return the updated alert as JSON
return response_success(data=alert_schema.dump(updated_alert))
except Exception as e:
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/batch/update', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_batch_update_route(caseid: int) -> Response:
"""
Update multiple alerts in the database
args:
caseid (int): The case id
returns:
Response: The response
"""
if not request.json:
return response_error('No JSON data provided')
# Load the JSON data from the request
data = request.get_json()
# Get the list of alert IDs and updates from the request data
alert_ids: List[int] = data.get('alert_ids', [])
updates = data.get('updates', {})
if not alert_ids:
return response_error('No alert IDs provided')
alert_schema = AlertSchema()
# Process each alert ID
for alert_id in alert_ids:
alert = get_alert_by_id(alert_id)
if not alert:
return response_error(f'Alert with ID {alert_id} not found')
try:
activity_data = []
for key, value in updates.items():
old_value = getattr(alert, key, None)
if old_value != value:
activity_data.append(f"\"{key}\" from \"{old_value}\" to \"{value}\"")
# Deserialize the JSON data into an Alert object
alert_schema.load(updates, instance=alert, partial=True)
db.session.commit()
alert = call_modules_hook('on_postload_alert_create', data=alert, caseid=caseid)
if activity_data:
track_activity(f"updated alerts #{alert_id}: {','.join(activity_data)}", ctx_less=True)
add_obj_history_entry(alert, f"updated alerts: {','.join(activity_data)}")
db.session.commit()
except Exception as e:
# Handle any errors during deserialization or DB operations
return response_error(str(e))
# Return a success response
return response_success(msg='Batch update successful')
@alerts_blueprint.route('/alerts/batch/delete', methods=['POST'])
@ac_api_requires(Permissions.alerts_delete, no_cid_required=True)
def alerts_batch_delete_route(caseid: int) -> Response:
"""
Delete multiple alerts from the database
args:
caseid (int): The case id
returns:
Response: The response
"""
if not request.json:
return response_error('No JSON data provided')
# Load the JSON data from the request
data = request.get_json()
# Get the list of alert IDs and updates from the request data
alert_ids: List[int] = data.get('alert_ids', [])
if not alert_ids:
return response_error('No alert IDs provided')
success, logs = delete_alerts(alert_ids)
if not success:
return response_error(logs)
alert = call_modules_hook('on_postload_alert_delete', data=f'alert_ids: {alert_ids}', caseid=caseid)
track_activity(f"deleted alerts #{','.join(str(alert_id) for alert_id in alert_ids)}", ctx_less=True)
return response_success(msg='Batch delete successful')
@alerts_blueprint.route('/alerts/delete/<int:alert_id>', methods=['POST'])
@ac_api_requires(Permissions.alerts_delete, no_cid_required=True)
def alerts_delete_route(alert_id, caseid) -> Response:
"""
Delete an alert from the database
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
alert = get_alert_by_id(alert_id)
if not alert:
return response_error('Alert not found')
try:
# Delete the case association
delete_similar_alert_cache(alert_id=alert_id)
# Delete the alert from the database
db.session.delete(alert)
db.session.commit()
alert = call_modules_hook('on_postload_alert_delete', data=alert_id, caseid=caseid)
track_activity(f"delete alert #{alert_id}", ctx_less=True)
# Return the deleted alert as JSON
return response_success(data={'alert_id': alert_id})
except Exception as e:
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/escalate/<int:alert_id>', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_escalate_route(alert_id, caseid) -> Response:
"""
Escalate an alert
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
alert = get_alert_by_id(alert_id)
if not alert:
return response_error('Alert not found')
if request.json is None:
return response_error('No JSON data provided')
data = request.get_json()
iocs_import_list: List[str] = data.get('iocs_import_list')
assets_import_list: List[str] = data.get('assets_import_list')
note: str = data.get('note')
import_as_event: bool = data.get('import_as_event')
case_tags: str = data.get('case_tags')
case_title: str = data.get('case_title')
case_template_id: int = data.get('case_template_id', None)
try:
# Escalate the alert to a case
alert.alert_status_id = AlertStatus.query.filter_by(status_name='Escalated').first().status_id
db.session.commit()
# Create a new case from the alert
case = create_case_from_alert(alert, iocs_list=iocs_import_list, assets_list=assets_import_list, note=note,
import_as_event=import_as_event, case_tags=case_tags, case_title=case_title,
template_id=case_template_id)
if not case:
return response_error('Failed to create case from alert')
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 from alert".format(case_name=case.name),
ctx_less=True)
add_obj_history_entry(alert, f"Alert escalated to case #{case.case_id}")
alert = call_modules_hook('on_postload_alert_escalate', data=alert, caseid=caseid)
# Return the updated alert as JSON
return response_success(data=CaseSchema().dump(case))
except Exception as e:
app.logger.exception(e)
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/merge/<int:alert_id>', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_merge_route(alert_id, caseid) -> Response:
"""
Merge an alert into a case
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
if request.json is None:
return response_error('No JSON data provided')
data = request.get_json()
target_case_id = data.get('target_case_id')
if target_case_id is None:
return response_error('No target case id provided')
alert = get_alert_by_id(alert_id)
if not alert:
return response_error('Alert not found')
case = get_case(target_case_id)
if not case:
return response_error('Target case not found')
iocs_import_list: List[str] = data.get('iocs_import_list')
assets_import_list: List[str] = data.get('assets_import_list')
note: str = data.get('note')
import_as_event: bool = data.get('import_as_event')
case_tags = data.get('case_tags')
try:
# Merge the alert into a case
alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id
db.session.commit()
# Merge alert in the case
merge_alert_in_case(alert, case,
iocs_list=iocs_import_list, assets_list=assets_import_list, note=note,
import_as_event=import_as_event, case_tags=case_tags)
alert = call_modules_hook('on_postload_alert_merge', data=alert, caseid=caseid)
track_activity(f"merge alert #{alert_id} into existing case #{target_case_id}", caseid=target_case_id)
add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}")
# Return the updated alert as JSON
return response_success(data=CaseSchema().dump(case))
except Exception as e:
app.app.logger.exception(e)
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/unmerge/<int:alert_id>', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_unmerge_route(alert_id, caseid) -> Response:
"""
Unmerge an alert from a case
args:
caseid (str): The case id
alert_id (int): The alert id
returns:
Response: The response
"""
if request.json is None:
return response_error('No JSON data provided')
target_case_id = request.json.get('target_case_id')
if target_case_id is None:
return response_error('No target case id provided')
alert = get_alert_by_id(alert_id)
if not alert:
return response_error('Alert not found')
case = get_case(target_case_id)
if not case:
return response_error('Target case not found')
try:
# Unmerge alert from the case
success, message = unmerge_alert_from_case(alert, case)
if success is False:
return response_error(message)
track_activity(f"unmerge alert #{alert_id} from case #{target_case_id}", caseid=target_case_id)
add_obj_history_entry(alert, f"Alert unmerged from case #{target_case_id}")
alert = call_modules_hook('on_postload_alert_unmerge', data=alert, caseid=caseid)
# Return the updated case as JSON
return response_success(data=AlertSchema().dump(alert), msg=message)
except Exception as e:
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/batch/merge', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_batch_merge_route(caseid) -> Response:
"""
Merge multiple alerts into a case
args:
caseid (str): The case id
returns:
Response: The response
"""
if request.json is None:
return response_error('No JSON data provided')
data = request.get_json()
target_case_id = data.get('target_case_id')
if target_case_id is None:
return response_error('No target case id provided')
alert_ids = data.get('alert_ids')
if not alert_ids:
return response_error('No alert ids provided')
case = get_case(target_case_id)
if not case:
return response_error('Target case not found')
iocs_import_list: List[str] = data.get('iocs_import_list')
assets_import_list: List[str] = data.get('assets_import_list')
note: str = data.get('note')
import_as_event: bool = data.get('import_as_event')
case_tags = data.get('case_tags')
try:
# Merge the alerts into a case
for alert_id in alert_ids.split(','):
alert_id = int(alert_id)
alert = get_alert_by_id(alert_id)
if not alert:
continue
alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id
db.session.commit()
# Merge alert in the case
merge_alert_in_case(alert, case, iocs_list=iocs_import_list, assets_list=assets_import_list, note=None,
import_as_event=import_as_event, case_tags=case_tags)
add_obj_history_entry(alert, f"Alert merged into existing case #{target_case_id}")
alert = call_modules_hook('on_postload_alert_merge', data=alert, caseid=caseid)
if note:
case.description += f"\n\n### Escalation note\n\n{note}\n\n" if case.description else f"\n\n{note}\n\n"
db.session.commit()
track_activity(f"batched merge alerts {alert_ids} into existing case #{target_case_id}",
caseid=target_case_id)
# Return the updated case as JSON
return response_success(data=CaseSchema().dump(case))
except Exception as e:
app.app.logger.exception(e)
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts/batch/escalate', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alerts_batch_escalate_route(caseid) -> Response:
"""
Escalate multiple alerts into a case
args:
caseid (str): The case id
returns:
Response: The response
"""
if request.json is None:
return response_error('No JSON data provided')
data = request.get_json()
alert_ids = data.get('alert_ids')
if not alert_ids:
return response_error('No alert ids provided')
iocs_import_list: List[str] = data.get('iocs_import_list')
assets_import_list: List[str] = data.get('assets_import_list')
note: str = data.get('note')
import_as_event: bool = data.get('import_as_event')
case_tags = data.get('case_tags')
case_title = data.get('case_title')
alerts_list = []
case_template_id: int = data.get('case_template_id', None)
try:
# Merge the alerts into a case
for alert_id in alert_ids.split(','):
alert_id = int(alert_id)
alert = get_alert_by_id(alert_id)
if not alert:
continue
alert.alert_status_id = AlertStatus.query.filter_by(status_name='Merged').first().status_id
db.session.commit()
alert = call_modules_hook('on_postload_alert_merge', data=alert, caseid=caseid)
alerts_list.append(alert)
# Merge alerts in the case
case = create_case_from_alerts(alerts_list, iocs_list=iocs_import_list, assets_list=assets_import_list,
note=note, import_as_event=import_as_event, case_tags=case_tags,
case_title=case_title, template_id=case_template_id)
if not case:
return response_error('Failed to create case from alert')
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 from alerts".format(case_name=case.name),
caseid=case.case_id)
for alert in alerts_list:
add_obj_history_entry(alert, f"Alert escalated into new case #{case.case_id}")
# Return the updated case as JSON
return response_success(data=CaseSchema().dump(case))
except Exception as e:
app.app.logger.exception(e)
# Handle any errors during deserialization or DB operations
return response_error(str(e))
@alerts_blueprint.route('/alerts', methods=['GET'])
@ac_requires(Permissions.alerts_read, no_cid_required=True)
def alerts_list_view_route(caseid, url_redir) -> Union[str, Response]:
"""
List all alerts
args:
caseid (str): The case id
returns:
Response: The response
"""
if url_redir:
return redirect(url_for('alerts.alerts_list_view_route', cid=caseid))
form = FlaskForm()
return render_template('alerts.html', caseid=caseid, form=form)
@alerts_blueprint.route('/alerts/<int:cur_id>/comments/modal', methods=['GET'])
@ac_requires(Permissions.alerts_read, no_cid_required=True)
def alert_comment_modal(cur_id, caseid, url_redir):
"""
Get the modal for the alert comments
args:
cur_id (int): The alert id
caseid (str): The case id
returns:
Response: The response
"""
if url_redir:
return redirect(url_for('alerts.alerts_list_view_route', cid=caseid, redirect=True))
alert = get_alert_by_id(cur_id)
if not alert:
return response_error('Invalid alert ID')
return render_template("modal_conversation.html", element_id=cur_id, element_type='alerts',
title=f" alert #{alert.alert_id}")
@alerts_blueprint.route('/alerts/<int:alert_id>/comments/list', methods=['GET'])
@ac_api_requires(Permissions.alerts_read, no_cid_required=True)
def alert_comments_get(alert_id, caseid):
"""
Get the comments for an alert
args:
alert_id (int): The alert id
caseid (str): The case id
returns:
Response: The response
"""
alert_comments = get_alert_comments(alert_id)
if alert_comments is None:
return response_error('Invalid alert ID')
return response_success(data=CommentSchema(many=True).dump(alert_comments))
@alerts_blueprint.route('/alerts/<int:alert_id>/comments/<int:com_id>/delete', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alert_comment_delete(alert_id, com_id, caseid):
"""
Delete a comment for an alert
args:
alert_id (int): The alert id
com_id (int): The comment id
caseid (str): The case id
returns:
Response: The response
"""
success, msg = delete_alert_comment(comment_id=com_id, alert_id=alert_id)
if not success:
return response_error(msg)
call_modules_hook('on_postload_alert_comment_delete', data=com_id, caseid=caseid)
track_activity(f"comment {com_id} on alert {alert_id} deleted", ctx_less=True)
return response_success(msg)
@alerts_blueprint.route('/alerts/<int:cur_id>/comments/<int:com_id>', methods=['GET'])
@ac_api_requires(Permissions.alerts_read, no_cid_required=True)
def alert_comment_get(cur_id, com_id, caseid):
"""
Get a comment for an alert
args:
cur_id (int): The alert id
com_id (int): The comment id
caseid (str): The case id
returns:
Response: The response
"""
comment = get_alert_comment(cur_id, com_id)
if not comment:
return response_error("Invalid comment ID")
return response_success(data=CommentSchema().dump(comment))
@alerts_blueprint.route('/alerts/<int:alert_id>/comments/<int:com_id>/edit', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def alert_comment_edit(alert_id, com_id, caseid):
"""
Edit a comment for an alert
args:
alert_id (int): The alert id
com_id (int): The comment id
caseid (str): The case id
returns:
Response: The response
"""
return case_comment_update(com_id, 'events', caseid=None)
@alerts_blueprint.route('/alerts/<int:alert_id>/comments/add', methods=['POST'])
@ac_api_requires(Permissions.alerts_write, no_cid_required=True)
def case_comment_add(alert_id, caseid):
"""
Add a comment to an alert
args:
alert_id (int): The alert id
caseid (str): The case id
returns:
Response: The response
"""
try:
alert = get_alert_by_id(alert_id=alert_id)
if not alert:
return response_error('Invalid alert ID')
comment_schema = CommentSchema()
comment = comment_schema.load(request.get_json())
comment.comment_alert_id = alert_id
comment.comment_user_id = current_user.id
comment.comment_date = datetime.now()
comment.comment_update_date = datetime.now()
db.session.add(comment)
db.session.commit()
add_obj_history_entry(alert, 'commented')
db.session.commit()
hook_data = {
"comment": comment_schema.dump(comment),
"alert": AlertSchema().dump(alert)
}
call_modules_hook('on_postload_alert_commented', data=hook_data, caseid=caseid)
track_activity(f"alert \"{alert.alert_id}\" commented", ctx_less=True)
return response_success("Alert commented", data=comment_schema.dump(comment))
except marshmallow.exceptions.ValidationError as e:
return response_error(msg="Data error", data=e.normalized_messages(), status=400)