Skip to content

Commit

Permalink
[FIX] Fixed alerts cusotm conditions and batch deletion of alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
whikernel committed Dec 9, 2024
1 parent f71442b commit db60762
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 70 deletions.
2 changes: 1 addition & 1 deletion source/app/blueprints/alerts/alerts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ def alerts_delete_route(alert_id) -> Response:
delete_similar_alert_cache(alert_id=alert_id)

# Delete the similarity entries
delete_related_alerts_cache(alert_id=alert_id)
delete_related_alerts_cache([alert_id])

# Delete the alert from the database
db.session.delete(alert)
Expand Down
180 changes: 141 additions & 39 deletions source/app/datamgmt/alerts/alerts_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,84 @@
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
alert_iocs_association, Ioc, IocLink, Client
from app.models.alerts import Alert, AlertStatus, AlertCaseAssociation, SimilarAlertsCache, AlertResolutionStatus, \
AlertSimilarity
from app.models.authorization import Permissions
AlertSimilarity, Severity
from app.models.authorization import Permissions, User
from app.schema.marshables import EventSchema, AlertSchema
from app.util import add_obj_history_entry


relationship_model_map = {
'owner': User,
'severity': Severity,
'status': AlertStatus,
'customer': Client,
'resolution_status': AlertResolutionStatus,
'cases': Cases,
'comments': Comments,
'assets': CaseAssets,
'iocs': Ioc
}


def db_list_all_alerts():
"""
List all alerts in the database
"""
return db.session.query(Alert).all()


def build_condition(column, operator, value):
# If 'column' is actually a relationship (e.g., Alert.owner),
# we need to find the corresponding foreign key column or raise an error.
if hasattr(column, 'property') and hasattr(column.property, 'local_columns'):
# It's a relationship attribute
fk_cols = list(column.property.local_columns)
if operator in ['in', 'not_in']:
if len(fk_cols) == 1:
# Use the single FK column for the condition
fk_col = fk_cols[0]
if operator == 'in':
return fk_col.in_(value)
else:
return ~fk_col.in_(value)
else:
raise NotImplementedError(
"in_() on a relationship with multiple FK columns not supported. Specify a direct column.")
else:
raise ValueError(
"Non-in operators on relationships require specifying a related model column, e.g., owner.id or assets.asset_name.")

# If we get here, 'column' should be an actual column, not a relationship.
if operator == 'not':
return column != value
elif operator == 'in':
return column.in_(value)
elif operator == 'not_in':
return ~column.in_(value)
elif operator == 'eq':
return column == value
elif operator == 'like':
return column.ilike(f"%{value}%")
else:
raise ValueError(f"Unsupported operator: {operator}")


def combine_conditions(conditions, logical_operator):
if len(conditions) > 1:
if logical_operator == 'or':
return or_(*conditions)
elif logical_operator == 'not':
return not_(and_(*conditions))
else: # Default to 'and'
return and_(*conditions)
elif conditions:
return conditions[0]
else:
return None


def get_filtered_alerts(
start_date: str = None,
end_date: str = None,
Expand Down Expand Up @@ -166,40 +229,68 @@ def get_filtered_alerts(
if clients_filters is not None:
conditions.append(Alert.alert_customer_id.in_(clients_filters))

query = db.session.query(
Alert
).options(
selectinload(Alert.severity),
selectinload(Alert.status),
selectinload(Alert.customer),
selectinload(Alert.cases),
selectinload(Alert.iocs),
selectinload(Alert.assets)
)

# 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
if isinstance(custom_conditions, str):
try:
custom_conditions = json.loads(custom_conditions)
except:
app.app.logger.exception(f"Error parsing custom_conditions: {custom_conditions}")
return

# Keep track of which relationships we've already joined
joined_relationships = set()

for custom_condition in custom_conditions:
field = getattr(Alert, custom_condition['field'])
field_path = 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)))
# Check if we need to handle a related field
if '.' in field_path:
relationship_name, related_field_name = field_path.split('.', 1)

# Ensure the relationship name is known
if relationship_name not in relationship_model_map:
raise ValueError(f"Unknown relationship: {relationship_name}")

related_model = relationship_model_map[relationship_name]

# Join the relationship if not already joined
if relationship_name not in joined_relationships:
query = query.join(getattr(Alert, relationship_name))
joined_relationships.add(relationship_name)

related_field = getattr(related_model, related_field_name, None)
if related_field is None:
raise ValueError(
f"Field '{related_field_name}' not found in related model '{related_model.__name__}'")

# Build the condition
condition = build_condition(related_field, operator, value)
conditions.append(condition)
else:
raise ValueError(f"Unsupported operator: {operator}")
# Field belongs to Alert model
field = getattr(Alert, field_path, None)
if field is None:
raise ValueError(f"Field '{field_path}' not found in Alert model")

# Combine conditions with the specified logical operator
if len(conditions) > 1:
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
condition = build_condition(field, operator, value)
conditions.append(condition)

# Combine conditions
combined_conditions = combine_conditions(conditions, logical_operator)

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

Expand All @@ -215,16 +306,6 @@ def get_filtered_alerts(

try:
# Query the alerts using the filter conditions
query = db.session.query(
Alert
).options(
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)
Expand Down Expand Up @@ -898,7 +979,7 @@ def delete_similar_alert_cache(alert_id):
db.session.commit()


def delete_related_alerts_cache(alert_id):
def delete_related_alert_cache(alert_id):
"""
Delete the related alerts cache
Expand Down Expand Up @@ -930,6 +1011,25 @@ def delete_similar_alerts_cache(alert_ids: List[int]):
db.session.commit()


def delete_related_alerts_cache(alert_ids: List[int]):
"""
Delete the related alerts cache
args:
alert_ids (List(int)): The ID of the alert
returns:
None
"""
AlertSimilarity.query.filter(
or_(
AlertSimilarity.alert_id.in_(alert_ids),
AlertSimilarity.similar_alert_id.in_(alert_ids)
)
).delete()
db.session.commit()


def get_related_alerts(customer_id, assets, iocs, details=False):
"""
Check if an alert is related to another alert
Expand Down Expand Up @@ -1362,6 +1462,8 @@ def delete_alerts(alert_ids: List[int]) -> tuple[bool, str]:

delete_similar_alerts_cache(alert_ids)

delete_related_alerts_cache(alert_ids)

remove_alerts_from_assets_by_ids(alert_ids)
remove_alerts_from_iocs_by_ids(alert_ids)
remove_case_alerts_by_ids(alert_ids)
Expand Down
52 changes: 22 additions & 30 deletions source/app/static/assets/js/iris/alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2162,36 +2162,28 @@ $(document).ready(function () {
let customCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
const completions = [
{
caption: '"field": "alert_owner_id"',
value: 'field',
meta: "field",
},
{
caption: '"operator": "in"',
value: 'operator',
meta: "operator",
},
{
caption: '"value": [1]',
value: 'value',
meta: "value",
},
{
caption: "in",
"value": "in",
"meta": "operator"
},
{
caption: "not",
"value": "in",
"meta": "operator"
},
{
caption: "not_in",
"value": "in",
"meta": "operator"
}
{ caption: '"field": "alert_title"', value: '"field": "alert_title"', meta: "field" },
{ caption: '"field": "alert_description"', value: '"field": "alert_description"', meta: "field" },
{ caption: '"field": "alert_source"', value: '"field": "alert_source"', meta: "field" },
{ caption: '"field": "alert_tags"', value: '"field": "alert_tags"', meta: "field" },
{ caption: '"field": "alert_status_id"', value: '"field": "alert_status_id"', meta: "field" },
{ caption: '"field": "alert_severity_id"', value: '"field": "alert_severity_id"', meta: "field" },
{ caption: '"field": "alert_classification_id"', value: '"field": "alert_classification_id"', meta: "field" },
{ caption: '"field": "alert_customer_id"', value: '"field": "alert_customer_id"', meta: "field" },
{ caption: '"field": "source_start_date"', value: '"field": "source_start_date"', meta: "field" },
{ caption: '"field": "source_end_date"', value: '"field": "source_end_date"', meta: "field" },
{ caption: '"field": "creation_start_date"', value: '"field": "creation_start_date"', meta: "field" },
{ caption: '"field": "creation_end_date"', value: '"field": "creation_end_date"', meta: "field" },
{ caption: '"field": "alert_assets"', value: '"field": "alert_assets"', meta: "field" },
{ caption: '"field": "alert_iocs"', value: '"field": "alert_iocs"', meta: "field" },
{ caption: '"field": "alert_ids"', value: '"field": "alert_ids"', meta: "field" },
{ caption: '"field": "source_reference"', value: '"field": "source_reference"', meta: "field" },
{ caption: '"field": "case_id"', value: '"field": "case_id"', meta: "field" },
{ caption: '"field": "alert_owner_id"', value: '"field": "alert_owner_id"', meta: "field" },
{ caption: '"field": "alert_resolution_id"', value: '"field": "alert_resolution_id"', meta: "field" },
{ caption: '"operator": "in"', value: '"operator": "in"', meta: "operator" },
{ caption: '"operator": "not_in"', value: '"operator": "not_in"', meta: "operator" },
{ caption: '"value": [1]', value: '"value": [1]', meta: "value" }
];

// Filter the completions based on the current prefix if desired
Expand Down

0 comments on commit db60762

Please sign in to comment.