update api

This commit is contained in:
funnywolf
2026-01-19 21:40:42 +08:00
parent 5bf12ee7a2
commit 28e05cb33f
3 changed files with 365 additions and 312 deletions
+72
View File
@@ -0,0 +1,72 @@
def create_alert_with_group_rule(alert: InputAlert, rule_def: GroupRule) -> str:
"""
Create alerts and cases using alert aggregation rules.
The function will automatically generate a deduplication fingerprint based on the definition of rule_def, and decide whether to create a new case or update an existing case.
"""
# alert
row_id_alert = Alert.create(alert)
artifacts = alert.get("artifact", [])
# case
timestamp = string_to_timestamp(alert["alert_date"], "%Y-%m-%dT%H:%M:%SZ")
deduplication_key = rule_def.generate_deduplication_key(artifacts=artifacts, timestamp=timestamp)
row = Case.get_by_deduplication_key(deduplication_key)
if row is None:
if rule_def.workbook is None:
workbook = "# There is no workbook for this source."
else:
workbook = rule_def.workbook
case_status_new = OptionSet.get_option_key_by_name_and_value("case_status", "New")
case: InputCase = {
"title": rule_def.generate_case_title(artifacts=artifacts),
"deduplication_key": deduplication_key,
"alert": [row_id_alert],
"case_status": case_status_new,
"created_date": get_current_time_str(),
"tags": alert["tags"],
"severity": alert["severity"],
"type": rule_def.source,
"description": alert["description"],
"workbook": workbook,
}
row_id_create = Case.create(case)
return row_id_create
else:
row_id_case = row.get("rowid")
existing_alerts = row.get("alert", [])
if row_id_alert not in existing_alerts:
existing_alerts.append(row_id_alert)
case_field = [
{"id": "alert", "value": existing_alerts},
]
# change case severity if new alert severity is higher
if rule_def.follow_alert_severity:
option_new_score = OptionSet.get_option_by_name_and_value("alert_case_severity", alert["severity"]).get("score", 0)
severity_value_exist = row.get("severity")
option_exist_score = OptionSet.get_option_by_name_and_value("alert_case_severity", severity_value_exist).get("score", 0)
if option_new_score > option_exist_score:
severity = alert["severity"]
else:
severity = severity_value_exist
case_field.append({"id": "severity", "value": severity})
# append alert tags to case tags
if rule_def.append_alert_tags:
tags_exist = row.get("tags", [])
for tag in alert["tags"]:
if tag not in tags_exist:
tags_exist.append(tag)
case_field.append({"id": "tags", "value": tags_exist, "type": 2})
row_id_updated = Case.update(row_id_case, case_field)
return row_id_updated
+286 -309
View File
@@ -1,73 +1,13 @@
import os
from enum import StrEnum
from typing import TypedDict, List, Optional, Union, Dict, Any, NotRequired, Literal
from typing import List, Dict, Any, Literal, Union
import requests
from pydantic import BaseModel
from Lib.api import string_to_timestamp, get_current_time_str
from Lib.log import logger
from PLUGINS.SIRP.CONFIG import SIRP_NOTICE_WEBHOOK
from PLUGINS.SIRP.grouprule import GroupRule
from PLUGINS.SIRP.nocolyapi import WorksheetRow, OptionSet, Group, Condition, Operator
from PLUGINS.SIRP.sirptype import EnrichmentModel, ArtifactModel
class InputCase(TypedDict):
"""
Need to be consistent with the SIRP Case table structure
If there are new fields in SIRP, they need to be added here
"""
title: str
deduplication_key: str
case_status: str
created_date: str
tags: List[str]
severity: str
type: str
description: str
workbook: str
# AI fields
confidence_ai: NotRequired[str]
analysis_rationale_ai: NotRequired[str]
recommended_actions_ai: NotRequired[str]
recommended_actions_ai: NotRequired[List[str]]
alert: List[str]
class InputAlert(TypedDict):
source: str
rule_id: str
rule_name: str
name: str
alert_date: str
created_date: str
tags: List[str]
severity: str
reference: NotRequired[str]
source_data_identifier: NotRequired[str]
description: str
summary_ai: NotRequired[Optional[Union[str, Dict[str, Any]]]]
artifact: List[Dict]
raw_log: NotRequired[Optional[Union[str, Dict[str, Any]]]]
class InputArtifact(TypedDict):
type: str
value: str
enrichment: NotRequired[Dict[str, Any]]
# def model_to_fields(model_instance: BaseModel) -> List[Dict[str, Any]]:
# fields = []
# model_dict = model_instance.model_dump(mode='json', exclude_unset=True)
#
# for key, value in model_dict.items():
# fields.append({'id': key, 'value': value})
#
# return fields
from PLUGINS.SIRP.sirptype import EnrichmentModel, ArtifactModel, AlertModel, CaseModel, TicketModel
def model_to_fields(model_instance: BaseModel) -> List[Dict[str, Any]]:
@@ -80,9 +20,6 @@ def model_to_fields(model_instance: BaseModel) -> List[Dict[str, Any]]:
'value': value
}
if field_info and field_info.json_schema_extra:
# custom_type = field_info.json_schema_extra.get('type')
# if custom_type is not None:
# field_item['type'] = custom_type
field_item.update(field_info.json_schema_extra)
fields.append(field_item)
return fields
@@ -109,6 +46,24 @@ class Enrichment(object):
model_list.append(EnrichmentModel(**one))
return model_list
@staticmethod
def list_by_rowids(rowids: Union[List[str], None], include_system_fields=True) -> Union[List[EnrichmentModel], List[str], None]:
if rowids is not None and rowids != []:
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=rowids
)
]
)
enrichment_list = Enrichment.list(filter_model, include_system_fields=include_system_fields)
return enrichment_list
else:
return rowids
@staticmethod
def update(model: EnrichmentModel) -> str:
if model.rowid is not None:
@@ -118,6 +73,24 @@ class Enrichment(object):
raise Exception("Enrichment rowid is None, cannot update.")
return rowid
@staticmethod
def batch_update(model_list: List[Union[EnrichmentModel, str]]) -> Union[List[str], None]:
if model_list is not None:
rowids = []
for model in model_list:
if isinstance(model, str):
rowids.append(model) # just link
continue
elif isinstance(model, EnrichmentModel):
rowid = Enrichment.update_or_create(model) # update or create record
rowids.append(rowid)
else:
raise Exception("Unsupported enrichment data type")
return rowids
else:
return model_list
@staticmethod
def create(model: EnrichmentModel) -> str:
fields = model_to_fields(model)
@@ -134,6 +107,88 @@ class Enrichment(object):
return rowid
class Ticket(object):
WORKSHEET_ID = "ticket"
def __init__(self):
pass
@staticmethod
def get(rowid, include_system_fields=True) -> TicketModel:
result = WorksheetRow.get(Ticket.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
model = TicketModel(**result)
return model
@staticmethod
def list(model: Group, include_system_fields=True) -> List[TicketModel]:
filter = model.model_dump()
result = WorksheetRow.list(Ticket.WORKSHEET_ID, filter, include_system_fields=include_system_fields)
model_list = []
for one in result:
model_list.append(TicketModel(**one))
return model_list
@staticmethod
def list_by_rowids(rowids: Union[List[str], None], include_system_fields=True) -> Union[List[TicketModel], List[str], None]:
if rowids is not None and rowids != []:
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=rowids
)
]
)
ticket_list = Ticket.list(filter_model, include_system_fields=include_system_fields)
return ticket_list
else:
return rowids
@staticmethod
def update(model: TicketModel) -> str:
if model.rowid is not None:
fields = model_to_fields(model)
rowid = WorksheetRow.update(Ticket.WORKSHEET_ID, model.rowid, fields)
else:
raise Exception("Ticket rowid is None, cannot update.")
return rowid
@staticmethod
def batch_update(model_list: List[Union[TicketModel, str]]) -> Union[List[str], None]:
if model_list is not None:
rowids = []
for model in model_list:
if isinstance(model, str):
rowids.append(model) # just link
continue
elif isinstance(model, TicketModel):
rowid = Ticket.update_or_create(model) # update or create record
rowids.append(rowid)
else:
raise Exception("Unsupported ticket data type")
return rowids
else:
return model_list
@staticmethod
def create(model: TicketModel) -> str:
fields = model_to_fields(model)
rowid = WorksheetRow.create(Ticket.WORKSHEET_ID, fields)
return rowid
@staticmethod
def update_or_create(model: TicketModel) -> str:
fields = model_to_fields(model)
if model.rowid is None:
rowid = WorksheetRow.create(Ticket.WORKSHEET_ID, fields)
else:
rowid = WorksheetRow.update(Ticket.WORKSHEET_ID, model.rowid, fields)
return rowid
class Artifact(object):
WORKSHEET_ID = "artifact"
@@ -144,20 +199,9 @@ class Artifact(object):
def get(rowid, include_system_fields=True) -> ArtifactModel:
result = WorksheetRow.get(Artifact.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
model = ArtifactModel(**result)
if model.enrichments is not None and model.enrichments != []:
# enrichments
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=model.enrichments
)
]
)
enrichment_list = Enrichment.list(filter_model)
model.enrichments = enrichment_list
# enrichments
model.enrichments = Enrichment.list_by_rowids(model.enrichments)
return model
@@ -168,40 +212,35 @@ class Artifact(object):
artifact_list = []
for artifact_data in result:
artifact_model = ArtifactModel(**artifact_data)
if artifact_model.enrichments is not None and artifact_model.enrichments != []:
# enrichments
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=artifact_model.enrichments
)
]
)
enrichment_list = Enrichment.list(filter_model)
artifact_model.enrichments = enrichment_list
# enrichments
artifact_model.enrichments = Enrichment.list_by_rowids(artifact_model.enrichments)
artifact_list.append(artifact_model)
return artifact_list
@staticmethod
def list_by_rowids(rowids: Union[List[str], None], include_system_fields=True) -> Union[List[ArtifactModel], List[str], None]:
if rowids is not None and rowids != []:
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=rowids
)
]
)
artifact_list = Artifact.list(filter_model, include_system_fields=include_system_fields)
return artifact_list
else:
return rowids
@staticmethod
def update_or_create(model: ArtifactModel) -> str:
# enrichments
if model.enrichments is not None:
enrichments_rowid_list = []
for enrichment in model.enrichments:
if isinstance(enrichment, str):
enrichments_rowid_list.append(enrichment) # just link
continue
elif isinstance(enrichment, EnrichmentModel):
rowid = Enrichment.update_or_create(enrichment) # update or create record
enrichments_rowid_list.append(rowid)
else:
raise Exception("Unsupported enrichment data type")
model.enrichments = enrichments_rowid_list
model.enrichments = Enrichment.batch_update(model.enrichments)
fields = model_to_fields(model)
if model.rowid is None:
@@ -210,104 +249,155 @@ class Artifact(object):
rowid = WorksheetRow.update(Artifact.WORKSHEET_ID, model.rowid, fields)
return rowid
@staticmethod
def batch_update(model_list: List[Union[ArtifactModel, str]]) -> Union[List[str], None]:
if model_list is not None:
rowids = []
for model in model_list:
if isinstance(model, str):
rowids.append(model) # just link
continue
elif isinstance(model, ArtifactModel):
rowid = Artifact.update_or_create(model) # update or create record
rowids.append(rowid)
else:
raise Exception("Unsupported enrichment data type")
return rowids
else:
return model_list
class Alert(object):
WORKSHEET_ID = "alert"
ARTIFACT_FIELD_ID = "artifacts"
def __init__(self):
pass
@staticmethod
def get(rowid, include_system_fields=False):
alert = WorksheetRow.get(Alert.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
artifacts = WorksheetRow.relations(Alert.WORKSHEET_ID, rowid, Alert.ARTIFACT_FIELD_ID, relation_worksheet_id=Artifact.WORKSHEET_ID,
include_system_fields=False)
alert[Alert.ARTIFACT_FIELD_ID] = artifacts
return alert
def get(rowid, include_system_fields=True) -> AlertModel:
result = WorksheetRow.get(Alert.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
model = AlertModel(**result)
# artifacts
model.artifacts = Artifact.list_by_rowids(model.artifacts)
# enrichments
model.enrichments = Enrichment.list_by_rowids(model.enrichments)
return model
@staticmethod
def update(rowid, fields: list):
row_id = WorksheetRow.update(Alert.WORKSHEET_ID, rowid, fields)
return row_id
def list(model: Group, include_system_fields=True) -> List[AlertModel]:
filter = model.model_dump()
result = WorksheetRow.list(Alert.WORKSHEET_ID, filter, include_system_fields=include_system_fields)
alert_list = []
for alert_data in result:
alert_model = AlertModel(**alert_data)
# artifacts
alert_model.artifacts = Artifact.list_by_rowids(alert_model.artifacts)
# enrichments
alert_model.enrichments = Enrichment.list_by_rowids(alert_model.enrichments)
alert_list.append(alert_model)
return alert_list
@staticmethod
def create(alert: InputAlert):
artifact_rowid_list = []
artifacts: list[dict] = alert.get("artifact", [])
for artifact in artifacts:
artifact_fields = [
{"id": "type", "value": artifact.get("type")},
{"id": "value", "value": artifact.get("value")},
{"id": "enrichment", "value": artifact.get("enrichment")},
]
artifact_filter = {
"type": "group",
"logic": "AND",
"children": [
{
"type": "condition",
"field": "type",
"operator": "eq",
"value": artifact.get("type")
},
{
"type": "condition",
"field": "value",
"operator": "eq",
"value": artifact.get("value")
}
def list_by_rowids(rowids: Union[List[str], None], include_system_fields=True) -> Union[List[AlertModel], List[str], None]:
if rowids is not None and rowids != []:
filter_model = Group(
logic="AND",
children=[
Condition(
field="rowid",
operator=Operator.IN,
value=rowids
)
]
}
)
model_list = Alert.list(filter_model, include_system_fields=include_system_fields)
return model_list
else:
return rowids
row_id_list = Artifact.update_or_create(artifact_fields, artifact_filter)
artifact_rowid_list.extend(row_id_list)
@staticmethod
def update_or_create(model: AlertModel) -> str:
if alert.get("created_date") is None:
alert["created_date"] = get_current_time_str()
# artifacts
model.artifacts = Artifact.batch_update(model.artifacts)
alert_fields = [
{"id": "tags", "value": alert.get("tags"), "type": 2},
{"id": "severity", "value": alert.get("severity")},
{"id": "source", "value": alert.get("source")},
{"id": "alert_date", "value": alert.get("alert_date")},
{"id": "created_date", "value": alert.get("created_date")},
{"id": "reference", "value": alert.get("reference")},
{"id": "description", "value": alert.get("description")},
{"id": "raw_log", "value": alert.get("raw_log")},
{"id": "rule_id", "value": alert.get("rule_id")},
{"id": "rule_name", "value": alert.get("rule_name")},
{"id": "name", "value": alert.get("name")},
{"id": "summary_ai", "value": alert.get("summary_ai")},
{"id": "artifact", "value": artifact_rowid_list},
]
# enrichments
model.enrichments = Enrichment.batch_update(model.enrichments)
# alert
row_id = WorksheetRow.create(Alert.WORKSHEET_ID, alert_fields)
fields = model_to_fields(model)
if model.rowid is None:
rowid = WorksheetRow.create(Alert.WORKSHEET_ID, fields)
else:
rowid = WorksheetRow.update(Alert.WORKSHEET_ID, model.rowid, fields)
return rowid
return row_id
@staticmethod
def batch_update(model_list: List[Union[AlertModel, str]]) -> Union[List[str], None]:
if model_list is not None:
rowids = []
for model in model_list:
if isinstance(model, str):
rowids.append(model) # just link
continue
elif isinstance(model, AlertModel):
rowid = Alert.update_or_create(model) # update or create record
rowids.append(rowid)
else:
raise Exception("Unsupported enrichment data type")
return rowids
else:
return model_list
class Case(object):
WORKSHEET_ID = "case"
ALERT_FIELD_ID = "alert"
def __init__(self):
pass
@staticmethod
def get(rowid, include_system_fields=False) -> InputCase:
case = WorksheetRow.get(Case.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
# alert id
alerts = WorksheetRow.relations(Case.WORKSHEET_ID, rowid, Case.ALERT_FIELD_ID, relation_worksheet_id=Alert.WORKSHEET_ID,
include_system_fields=include_system_fields)
for alert in alerts:
artifacts = WorksheetRow.relations(Alert.WORKSHEET_ID, alert.get("rowid"), Alert.ARTIFACT_FIELD_ID, relation_worksheet_id=Artifact.WORKSHEET_ID,
include_system_fields=include_system_fields)
alert[Alert.ARTIFACT_FIELD_ID] = artifacts
case[Case.ALERT_FIELD_ID] = alerts
return case
def get(rowid, include_system_fields=True) -> CaseModel:
result = WorksheetRow.get(Case.WORKSHEET_ID, rowid, include_system_fields=include_system_fields)
model = CaseModel(**result)
# alerts
model.alerts = Alert.list_by_rowids(model.alerts)
# enrichments
model.enrichments = Enrichment.list_by_rowids(model.enrichments)
# tickets
model.tickets = Ticket.list_by_rowids(model.tickets)
return model
@staticmethod
def list(model: Group, include_system_fields=True) -> List[CaseModel]:
filter = model.model_dump()
result = WorksheetRow.list(Case.WORKSHEET_ID, filter, include_system_fields=include_system_fields)
case_list = []
for case_data in result:
case_model = CaseModel(**case_data)
# alerts
case_model.alerts = Alert.list_by_rowids(case_model.alerts)
# enrichments
case_model.enrichments = Enrichment.list_by_rowids(case_model.enrichments)
# tickets
case_model.tickets = Ticket.list_by_rowids(case_model.tickets)
case_list.append(case_model)
return case_list
@staticmethod
def get_raw_data(rowid, include_system_fields=False) -> Dict:
@@ -321,14 +411,14 @@ class Case(object):
case_clean = {key: case[key] for key in useful_case_fields if key in case}
# alert id
alerts = WorksheetRow.relations(Case.WORKSHEET_ID, rowid, Case.ALERT_FIELD_ID, relation_worksheet_id=Alert.WORKSHEET_ID,
alerts = WorksheetRow.relations(Case.WORKSHEET_ID, rowid, "alerts", relation_worksheet_id=Alert.WORKSHEET_ID,
include_system_fields=include_system_fields)
alerts_clean = []
for alert in alerts:
useful_alert_fields = ["rowid", 'severity', 'rule_id', 'rule_name', 'id']
alert_clean = {key: alert[key] for key in useful_alert_fields if key in alert}
artifacts = WorksheetRow.relations(Alert.WORKSHEET_ID, alert.get("rowid"), Alert.ARTIFACT_FIELD_ID, relation_worksheet_id=Artifact.WORKSHEET_ID,
artifacts = WorksheetRow.relations(Alert.WORKSHEET_ID, alert.get("rowid"), "artifacts", relation_worksheet_id=Artifact.WORKSHEET_ID,
include_system_fields=include_system_fields)
artifacts_clean = []
for artifact in artifacts:
@@ -336,55 +426,30 @@ class Case(object):
artifact_clean = {key: artifact[key] for key in useful_artifact_fields if key in artifact}
artifacts_clean.append(artifact_clean)
alert_clean[Alert.ARTIFACT_FIELD_ID] = artifacts_clean
alert_clean["artifacts"] = artifacts_clean
alerts_clean.append(alert_clean)
case_clean[Case.ALERT_FIELD_ID] = alerts_clean
case_clean["alerts"] = alerts_clean
return case_clean
@staticmethod
def create(case: InputCase):
case_fields = [
{"id": "title", "value": case["title"]},
{"id": "deduplication_key", "value": case["deduplication_key"]},
{"id": "alert", "value": case["alert"]},
{"id": "case_status", "value": case["case_status"]},
{"id": "created_date", "value": case["created_date"]},
{"id": "tags", "value": case["tags"], "type": 2},
{"id": "severity", "value": case["severity"]},
{"id": "type", "value": case["type"]},
{"id": "description", "value": case["description"]},
{"id": "workbook", "value": case["workbook"]},
]
row_id = WorksheetRow.create(Case.WORKSHEET_ID, case_fields)
return row_id
def update_or_create(model: CaseModel) -> str:
@staticmethod
def update(row_id, fields: list):
row_id = WorksheetRow.update(Case.WORKSHEET_ID, row_id, fields)
return row_id
# alerts
model.alerts = Alert.batch_update(model.alerts)
@staticmethod
def get_by_deduplication_key(deduplication_key: str):
filter = {
"type": "group",
"logic": "AND",
"children": [
{
"type": "condition",
"field": "deduplication_key",
"operator": "eq",
"value": deduplication_key
},
]
}
rows = WorksheetRow.list(Case.WORKSHEET_ID, filter)
if rows:
if len(rows) > 1:
logger.warning(f"found multiple rows with deduplication_key {deduplication_key}")
return rows[0]
# enrichments
model.enrichments = Enrichment.batch_update(model.enrichments)
# tickets
model.tickets = Ticket.batch_update(model.tickets)
fields = model_to_fields(model)
if model.rowid is None:
rowid = WorksheetRow.create(Case.WORKSHEET_ID, fields)
else:
return None
rowid = WorksheetRow.update(Case.WORKSHEET_ID, model.rowid, fields)
return rowid
@staticmethod
def get_by_case_id(case_id: str):
@@ -408,21 +473,6 @@ class Case(object):
else:
return None
@staticmethod
def load_workbook_md(workbook_name: str) -> str:
## TODO remove this function
"""
Read the content of DATA/WORKBOOK/{workbook_name}.md according to the workbook name and return a string.
The path is relative to the project root (two levels up to the asf folder).
"""
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
md_path = os.path.join(base_dir, 'DATA', 'WORKBOOK', f"{workbook_name}.md")
if not os.path.exists(md_path):
raise FileNotFoundError(f"workbook md not found: {md_path}")
with open(md_path, 'r', encoding='utf-8') as f:
return f.read()
class PlaybookStatus(StrEnum):
SUCCESS = 'Success'
@@ -574,76 +624,3 @@ class Notice(object):
def send(user, title, body=None):
result = requests.post(SIRP_NOTICE_WEBHOOK, json={"title": title, "body": body, "user": user})
return result
def create_alert_with_group_rule(alert: InputAlert, rule_def: GroupRule) -> str:
"""
Create alerts and cases using alert aggregation rules.
The function will automatically generate a deduplication fingerprint based on the definition of rule_def, and decide whether to create a new case or update an existing case.
"""
# alert
row_id_alert = Alert.create(alert)
artifacts = alert.get("artifact", [])
# case
timestamp = string_to_timestamp(alert["alert_date"], "%Y-%m-%dT%H:%M:%SZ")
deduplication_key = rule_def.generate_deduplication_key(artifacts=artifacts, timestamp=timestamp)
row = Case.get_by_deduplication_key(deduplication_key)
if row is None:
if rule_def.workbook is None:
workbook = "# There is no workbook for this source."
else:
workbook = rule_def.workbook
case_status_new = OptionSet.get_option_key_by_name_and_value("case_status", "New")
case: InputCase = {
"title": rule_def.generate_case_title(artifacts=artifacts),
"deduplication_key": deduplication_key,
"alert": [row_id_alert],
"case_status": case_status_new,
"created_date": get_current_time_str(),
"tags": alert["tags"],
"severity": alert["severity"],
"type": rule_def.source,
"description": alert["description"],
"workbook": workbook,
}
row_id_create = Case.create(case)
return row_id_create
else:
row_id_case = row.get("rowid")
existing_alerts = row.get("alert", [])
if row_id_alert not in existing_alerts:
existing_alerts.append(row_id_alert)
case_field = [
{"id": "alert", "value": existing_alerts},
]
# change case severity if new alert severity is higher
if rule_def.follow_alert_severity:
option_new_score = OptionSet.get_option_by_name_and_value("alert_case_severity", alert["severity"]).get("score", 0)
severity_value_exist = row.get("severity")
option_exist_score = OptionSet.get_option_by_name_and_value("alert_case_severity", severity_value_exist).get("score", 0)
if option_new_score > option_exist_score:
severity = alert["severity"]
else:
severity = severity_value_exist
case_field.append({"id": "severity", "value": severity})
# append alert tags to case tags
if rule_def.append_alert_tags:
tags_exist = row.get("tags", [])
for tag in alert["tags"]:
if tag not in tags_exist:
tags_exist.append(tag)
case_field.append({"id": "tags", "value": tags_exist, "type": 2})
row_id_updated = Case.update(row_id_case, case_field)
return row_id_updated
+7 -3
View File
@@ -236,11 +236,15 @@ class AlertModel(BaseSystemModel):
raw_data: Optional[str] = Field(default="", description="原始告警日志,通常为JSON格式的字符串")
attachments: Optional[List[Union[AttachmentModel, str]]] = Field(default=[], description="告警的附件")
# AI字段
summary_ai: Optional[str] = Field(default="", description="AI提供的汇总摘要")
# 反向关联
case: Optional[str] = Field(default=None, description="此告警关联到的安全事件(Case)(只保留rowid,避免循环引用)")
# 关联表
case: Optional[Union[CaseModel, str]] = Field(default=None, description="此告警关联到的安全事件(Case")
attachments: Optional[List[Union[AttachmentModel, str]]] = Field(default=[], description="告警的附件")
artifacts: Optional[List[Union[ArtifactModel, str]]] = Field(default=[], description="从告警中提取出的实体(Artifact)列表")
enrichments: Optional[List[Union[EnrichmentModel, str]]] = Field(default=[], description="对整个告警进行的富化结果")
@@ -269,6 +273,7 @@ class CaseModel(BaseSystemModel):
assignee_l1: Optional[AccountModel] = Field(default=None, description="分配给L1一线分析师")
acknowledged_time: Optional[Union[datetime, str]] = Field(default=None, description="L1分析师首次确认接收事件的时间")
comment: Optional[str] = Field(default="", description="分析师对整个事件的评论或处置记录")
attachments: Optional[List[Union[AttachmentModel, str]]] = Field(default=[], description="与事件相关的附件列表")
assignee_l2: Optional[AccountModel] = Field(default=None, description="分配或升级给L2二线分析师")
assignee_l3: Optional[AccountModel] = Field(default=None, description="分配或升级给L3专家分析师")
@@ -300,7 +305,6 @@ class CaseModel(BaseSystemModel):
respond_time: Optional[Any] = Field(default=None, description="事件处置完成时间(closed_time), 用于计算MTTR")
# 关联表
attachments: Optional[List[Union[AttachmentModel, str]]] = Field(default=[], description="与事件相关的附件列表")
tickets: Optional[List[Union[TicketModel, str]]] = Field(default=[], description="与此事件关联的外部工单列表")
enrichments: Optional[List[Union[EnrichmentModel, str]]] = Field(default=[], description="对整个事件进行的富化结果")
alerts: Optional[List[Union[AlertModel, str]]] = Field(default=[], description="合并到此事件中的告警列表")