Skip to content

Commit

Permalink
[ADD] Custom filtering option in alerts UI
Browse files Browse the repository at this point in the history
  • Loading branch information
whikernel committed Dec 9, 2024
1 parent 7088a73 commit 461182c
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 45 deletions.
64 changes: 39 additions & 25 deletions source/app/blueprints/alerts/alerts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,31 +110,45 @@ def alerts_list_route() -> Response:
except ValueError:
return response_error('Invalid alert ioc')

filtered_data = get_filtered_alerts(
start_date=request.args.get('creation_start_date'),
end_date=request.args.get('creation_end_date'),
source_start_date=request.args.get('source_start_date'),
source_end_date=request.args.get('source_end_date'),
source_reference=request.args.get('source_reference'),
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),
current_user_id=current_user.id
)
fields_str = request.args.get('fields')
if fields_str:
# Split into a list
fields = [field.strip() for field in fields_str.split(',') if field.strip()]
else:
fields = None

try:
filtered_data = get_filtered_alerts(
start_date=request.args.get('creation_start_date'),
end_date=request.args.get('creation_end_date'),
source_start_date=request.args.get('source_start_date'),
source_end_date=request.args.get('source_end_date'),
source_reference=request.args.get('source_reference'),
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', 'desc', type=str),
custom_conditions=request.args.get('custom_conditions'),
assets=alert_assets,
iocs=alert_iocs,
resolution_status=request.args.get('alert_resolution_id', type=int),
current_user_id=current_user.id,
fields=fields
)

except Exception as e:
app.app.logger.exception(e)
return response_error(str(e))

if filtered_data is None:
return response_error('Filtering error')
Expand Down
16 changes: 16 additions & 0 deletions source/app/blueprints/alerts/templates/alerts.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,22 @@
</select>
</div>
</div>
<div class="form-row mt-3">
<div class="col-md-12">
<button class="btn btn-sm btn-outline-dark" type="button" data-toggle="collapse" data-target="#customConditionsCollapse" aria-expanded="false" aria-controls="customConditionsCollapse">
Custom Conditions
</button>
</div>
</div>

<div class="collapse mt-3" id="customConditionsCollapse">
<div class="form-row">
<div class="col-md-12 form-group">
<label for="custom_conditions">Custom Conditions</label>
<div class="form-control" id="custom_conditions"></div>
</div>
</div>
</div>
<div class="form-row mt-3">
<div class="col centered">
<button type="submit" class="btn btn-sm btn-primary">Apply Filters</button>
Expand Down
93 changes: 76 additions & 17 deletions source/app/datamgmt/alerts/alerts_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from flask_login import current_user
from functools import reduce
from operator import and_
from sqlalchemy import desc, asc, func, tuple_, or_
from sqlalchemy import desc, asc, func, tuple_, or_, not_
from sqlalchemy.orm import aliased, make_transient, selectinload
from typing import List, Tuple, Dict

Expand All @@ -36,11 +36,13 @@
from app.datamgmt.manage.manage_case_templates_db import get_case_template_by_id, \
case_template_post_modifier
from app.datamgmt.states import update_timeline_state
from app.iris_engine.access_control.utils import ac_current_user_has_permission
from app.iris_engine.utils.common import parse_bf_date_format
from app.models import Cases, EventCategory, Tags, AssetsType, Comments, CaseAssets, alert_assets_association, \
alert_iocs_association, Ioc, IocLink
from app.models.alerts import Alert, AlertStatus, AlertCaseAssociation, SimilarAlertsCache, AlertResolutionStatus, \
AlertSimilarity
from app.models.authorization import Permissions
from app.schema.marshables import EventSchema, AlertSchema
from app.util import add_obj_history_entry

Expand All @@ -57,7 +59,6 @@ def get_filtered_alerts(
end_date: str = None,
source_start_date: str = None,
source_end_date: str = None,
source_reference: str = None,
title: str = None,
description: str = None,
status: int = None,
Expand All @@ -71,19 +72,27 @@ def get_filtered_alerts(
alert_ids: List[int] = None,
assets: List[str] = None,
iocs: List[str] = None,
resolution_status: int = None,
resolution_status: List[int] = None,
logical_operator: str = 'and', # Logical operator: 'and', 'or', 'not'
page: int = 1,
per_page: int = 10,
sort: str = 'desc',
current_user_id: int = None
):
current_user_id: int = None,
source_reference=None,
custom_conditions: List[dict] = None,
fields: List[str] = None):
"""
Get a list of alerts that match the given filter conditions
args:
start_date (datetime): The start date of the alert creation time
end_date (datetime): The end date of the alert creation time
...
fields (List[str]): The list of fields to include in the output
returns:
dict: A dictionary containing the total count, alerts, and pagination information
dict: Dictionary with pagination info and list of serialized alerts
"""
# Build the filter conditions
conditions = []

if start_date is not None and end_date is not None:
Expand Down Expand Up @@ -111,7 +120,10 @@ def get_filtered_alerts(
conditions.append(Alert.alert_severity_id == severity)

if resolution_status is not None:
conditions.append(Alert.alert_resolution_status_id == resolution_status)
if isinstance(resolution_status, list):
conditions.append(not_(Alert.alert_resolution_status_id.in_(resolution_status)))
else:
conditions.append(Alert.alert_resolution_status_id == resolution_status)

if source_reference is not None:
conditions.append(Alert.alert_source_ref.like(f'%{source_reference}%'))
Expand Down Expand Up @@ -149,28 +161,75 @@ def get_filtered_alerts(
if isinstance(iocs, list):
conditions.append(Alert.iocs.any(Ioc.ioc_value.in_(iocs)))

if current_user_id is not None:
if current_user_id is not None and not ac_current_user_has_permission(Permissions.server_administrator):
clients_filters = get_user_clients_id(current_user_id)
if clients_filters is not None:
conditions.append(Alert.alert_customer_id.in_(clients_filters))

# Apply custom conditions if provided
if custom_conditions:
try:
custom_conditions = json.loads(custom_conditions)
except:
app.app.logger.exception(f"Error parsing custom_conditions: {custom_conditions}")
return

for custom_condition in custom_conditions:
field = getattr(Alert, custom_condition['field'])
operator = custom_condition['operator']
value = custom_condition['value']

if operator == 'not':
conditions.append(not_(field == value))
elif operator == 'in':
conditions.append(field.in_(value))
elif operator == 'not_in':
conditions.append(not_(field.in_(value)))
else:
raise ValueError(f"Unsupported operator: {operator}")

# Combine conditions with the specified logical operator
if len(conditions) > 1:
conditions = [reduce(and_, conditions)]
if logical_operator == 'or':
combined_conditions = or_(*conditions)
elif logical_operator == 'not':
combined_conditions = not_(and_(*conditions))
else: # Default to 'and'
combined_conditions = and_(*conditions)
elif conditions:
combined_conditions = conditions[0]
else:
combined_conditions = None

order_func = desc if sort == "desc" else asc

alert_schema = AlertSchema()
# If fields are provided, use them in the schema
if fields:
try:
alert_schema = AlertSchema(only=fields)
except Exception as e:
app.app.logger.exception(f"Error selecting fields in AlertSchema: {str(e)}")
alert_schema = AlertSchema()
else:
alert_schema = AlertSchema()

try:
# Query the alerts using the filter conditions
filtered_alerts = db.session.query(
query = db.session.query(
Alert
).filter(
*conditions
).options(
selectinload(Alert.severity), selectinload(Alert.status), selectinload(Alert.customer), selectinload(Alert.cases),
selectinload(Alert.iocs), selectinload(Alert.assets)
).order_by(
selectinload(Alert.severity),
selectinload(Alert.status),
selectinload(Alert.customer),
selectinload(Alert.cases),
selectinload(Alert.iocs),
selectinload(Alert.assets)
)

if combined_conditions is not None:
query = query.filter(combined_conditions)

filtered_alerts = query.order_by(
order_func(Alert.alert_source_event_time)
).paginate(page=page, per_page=per_page, error_out=False)

Expand Down
Loading

0 comments on commit 461182c

Please sign in to comment.