@@ -24,6 +24,13 @@ def __init__(
2424 self .verify_ssl = verify_ssl
2525 self .timeout_seconds = timeout_seconds
2626
27+ _SEVERITY_TO_ID = {
28+ "critical" : 1 ,
29+ "high" : 2 ,
30+ "medium" : 3 ,
31+ "low" : 4 ,
32+ }
33+
2734 @classmethod
2835 def from_env (cls ) -> "IrisClient" :
2936 base_url = os .getenv ("IRIS_BASE_URL" , "" ).strip ()
@@ -44,6 +51,43 @@ def _headers(self) -> dict[str, str]:
4451 "Content-Type" : "application/json" ,
4552 }
4653
54+ def _normalize_case_payload (
55+ self ,
56+ * ,
57+ case_payload : dict [str , Any ],
58+ fallback_case_id : str ,
59+ fallback_case_name : str ,
60+ fallback_description : str ,
61+ fallback_severity : str ,
62+ fallback_tags : list [str ] | None = None ,
63+ ) -> dict [str , Any ]:
64+ case_id = str (case_payload .get ("case_id" , case_payload .get ("id" , fallback_case_id )))
65+ return {
66+ "source_system" : "iris" ,
67+ "case_id" : case_id ,
68+ "report_id" : str (case_payload .get ("report_id" , case_payload .get ("id" , case_id ))),
69+ "report_url" : case_payload .get ("report_url" ) or f"{ self .base_url } /case/{ case_id } " ,
70+ "ingested_at" : case_payload .get ("modification_date" ) or case_payload .get ("created_at" ),
71+ "case_name" : case_payload .get ("case_name" ) or case_payload .get ("name" ) or fallback_case_name ,
72+ "short_description" : case_payload .get ("case_description" )
73+ or case_payload .get ("description" )
74+ or fallback_description ,
75+ "severity" : str (case_payload .get ("severity" , fallback_severity )),
76+ "tags" : case_payload .get ("tags" ) or fallback_tags or [],
77+ "iocs" : case_payload .get ("iocs" , []),
78+ "timeline" : case_payload .get ("timeline" , []),
79+ }
80+
81+ def _severity_id_from_label (self , severity : str ) -> int :
82+ normalized = severity .strip ().lower ()
83+ if normalized in self ._SEVERITY_TO_ID :
84+ return self ._SEVERITY_TO_ID [normalized ]
85+
86+ if normalized .isdigit () and int (normalized ) > 0 :
87+ return int (normalized )
88+
89+ return self ._SEVERITY_TO_ID ["medium" ]
90+
4791 def _extract_case_payload (self , payload : Any , case_id : str ) -> dict [str , Any ]:
4892 if isinstance (payload , dict ):
4993 data = payload .get ("data" , payload )
@@ -77,25 +121,83 @@ def fetch_case(self, case_id: str) -> dict[str, Any]:
77121
78122 payload = response .json ()
79123 case_payload = self ._extract_case_payload (payload , case_id )
80- return {
81- "source_system" : "iris" ,
82- "case_id" : str (case_payload .get ("case_id" , case_payload .get ("id" , case_id ))),
83- "report_id" : str (case_payload .get ("report_id" , case_payload .get ("id" , case_id ))),
84- "report_url" : case_payload .get ("report_url" ) or f"{ self .base_url } /case/{ case_id } " ,
85- "ingested_at" : case_payload .get ("modification_date" ) or case_payload .get ("created_at" ),
86- "case_name" : case_payload .get ("case_name" )
87- or case_payload .get ("name" )
88- or f"IRIS Case { case_id } " ,
89- "short_description" : case_payload .get ("case_description" )
90- or case_payload .get ("description" )
91- or "No case description provided." ,
92- "severity" : str (case_payload .get ("severity" , "unknown" )),
93- "tags" : case_payload .get ("tags" , []),
94- "iocs" : case_payload .get ("iocs" , []),
95- "timeline" : case_payload .get ("timeline" , []),
96- }
124+ return self ._normalize_case_payload (
125+ case_payload = case_payload ,
126+ fallback_case_id = case_id ,
127+ fallback_case_name = f"IRIS Case { case_id } " ,
128+ fallback_description = "No case description provided." ,
129+ fallback_severity = "unknown" ,
130+ )
97131 except (httpx .HTTPError , ValueError , IrisClientError ) as exc :
98132 last_error = str (exc )
99133 continue
100134
101135 raise IrisClientError (f"Failed to fetch case { case_id } from IRIS: { last_error or 'unknown error' } " )
136+
137+ def create_incident (
138+ self ,
139+ * ,
140+ case_name : str ,
141+ case_description : str ,
142+ severity : str = "medium" ,
143+ tags : list [str ] | None = None ,
144+ case_customer : int = 1 ,
145+ case_soc_id : str = "" ,
146+ classification_id : int | None = None ,
147+ case_template_id : str | None = None ,
148+ custom_attributes : dict [str , Any ] | None = None ,
149+ ) -> dict [str , Any ]:
150+ normalized_name = case_name .strip ()
151+ normalized_description = case_description .strip ()
152+ if not normalized_name :
153+ raise IrisClientError ("case_name must be provided" )
154+ if not normalized_description :
155+ raise IrisClientError ("case_description must be provided" )
156+
157+ payload : dict [str , Any ] = {
158+ "case_name" : normalized_name ,
159+ "case_description" : normalized_description ,
160+ "case_customer" : case_customer ,
161+ "case_soc_id" : case_soc_id ,
162+ "severity_id" : self ._severity_id_from_label (severity ),
163+ }
164+
165+ if tags :
166+ payload ["case_tags" ] = "," .join (item .strip () for item in tags if item .strip ())
167+ if classification_id is not None :
168+ payload ["classification_id" ] = classification_id
169+ if case_template_id :
170+ payload ["case_template_id" ] = str (case_template_id )
171+ if custom_attributes is not None :
172+ payload ["custom_attributes" ] = custom_attributes
173+
174+ endpoints : list [tuple [str , str ]] = [
175+ ("POST" , "/manage/cases/add" ),
176+ ]
177+
178+ last_error : str | None = None
179+ with httpx .Client (timeout = self .timeout_seconds , verify = self .verify_ssl ) as client :
180+ for method , path in endpoints :
181+ url = f"{ self .base_url } { path } "
182+ try :
183+ response = client .request (method = method , url = url , json = payload , headers = self ._headers ())
184+ if response .status_code >= 400 :
185+ last_error = f"{ method } { path } returned { response .status_code } "
186+ continue
187+
188+ body = response .json ()
189+ case_payload = self ._extract_case_payload (body , case_id = "new" )
190+ created_case_id = str (case_payload .get ("case_id" , case_payload .get ("id" , "new" )))
191+ return self ._normalize_case_payload (
192+ case_payload = case_payload ,
193+ fallback_case_id = created_case_id ,
194+ fallback_case_name = normalized_name ,
195+ fallback_description = normalized_description ,
196+ fallback_severity = severity ,
197+ fallback_tags = tags ,
198+ )
199+ except (httpx .HTTPError , ValueError , IrisClientError ) as exc :
200+ last_error = str (exc )
201+ continue
202+
203+ raise IrisClientError (f"Failed to create IRIS incident: { last_error or 'unknown error' } " )
0 commit comments