iflow-mcp-m507_ai-soc-agent 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/METADATA +410 -0
  2. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/RECORD +85 -0
  3. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_m507_ai_soc_agent-1.0.0.dist-info/top_level.txt +1 -0
  7. src/__init__.py +8 -0
  8. src/ai_controller/README.md +139 -0
  9. src/ai_controller/__init__.py +12 -0
  10. src/ai_controller/agent_executor.py +596 -0
  11. src/ai_controller/cli/__init__.py +2 -0
  12. src/ai_controller/cli/main.py +243 -0
  13. src/ai_controller/session_manager.py +409 -0
  14. src/ai_controller/web/__init__.py +2 -0
  15. src/ai_controller/web/server.py +1181 -0
  16. src/ai_controller/web/static/css/README.md +102 -0
  17. src/api/__init__.py +13 -0
  18. src/api/case_management.py +271 -0
  19. src/api/edr.py +187 -0
  20. src/api/kb.py +136 -0
  21. src/api/siem.py +308 -0
  22. src/core/__init__.py +10 -0
  23. src/core/config.py +242 -0
  24. src/core/config_storage.py +684 -0
  25. src/core/dto.py +50 -0
  26. src/core/errors.py +36 -0
  27. src/core/logging.py +128 -0
  28. src/integrations/__init__.py +8 -0
  29. src/integrations/case_management/__init__.py +5 -0
  30. src/integrations/case_management/iris/__init__.py +11 -0
  31. src/integrations/case_management/iris/iris_client.py +885 -0
  32. src/integrations/case_management/iris/iris_http.py +274 -0
  33. src/integrations/case_management/iris/iris_mapper.py +263 -0
  34. src/integrations/case_management/iris/iris_models.py +128 -0
  35. src/integrations/case_management/thehive/__init__.py +8 -0
  36. src/integrations/case_management/thehive/thehive_client.py +193 -0
  37. src/integrations/case_management/thehive/thehive_http.py +147 -0
  38. src/integrations/case_management/thehive/thehive_mapper.py +190 -0
  39. src/integrations/case_management/thehive/thehive_models.py +125 -0
  40. src/integrations/cti/__init__.py +6 -0
  41. src/integrations/cti/local_tip/__init__.py +10 -0
  42. src/integrations/cti/local_tip/local_tip_client.py +90 -0
  43. src/integrations/cti/local_tip/local_tip_http.py +110 -0
  44. src/integrations/cti/opencti/__init__.py +10 -0
  45. src/integrations/cti/opencti/opencti_client.py +101 -0
  46. src/integrations/cti/opencti/opencti_http.py +418 -0
  47. src/integrations/edr/__init__.py +6 -0
  48. src/integrations/edr/elastic_defend/__init__.py +6 -0
  49. src/integrations/edr/elastic_defend/elastic_defend_client.py +351 -0
  50. src/integrations/edr/elastic_defend/elastic_defend_http.py +162 -0
  51. src/integrations/eng/__init__.py +10 -0
  52. src/integrations/eng/clickup/__init__.py +8 -0
  53. src/integrations/eng/clickup/clickup_client.py +513 -0
  54. src/integrations/eng/clickup/clickup_http.py +156 -0
  55. src/integrations/eng/github/__init__.py +8 -0
  56. src/integrations/eng/github/github_client.py +169 -0
  57. src/integrations/eng/github/github_http.py +158 -0
  58. src/integrations/eng/trello/__init__.py +8 -0
  59. src/integrations/eng/trello/trello_client.py +207 -0
  60. src/integrations/eng/trello/trello_http.py +162 -0
  61. src/integrations/kb/__init__.py +12 -0
  62. src/integrations/kb/fs_kb_client.py +313 -0
  63. src/integrations/siem/__init__.py +6 -0
  64. src/integrations/siem/elastic/__init__.py +6 -0
  65. src/integrations/siem/elastic/elastic_client.py +3319 -0
  66. src/integrations/siem/elastic/elastic_http.py +165 -0
  67. src/mcp/README.md +183 -0
  68. src/mcp/TOOLS.md +2827 -0
  69. src/mcp/__init__.py +13 -0
  70. src/mcp/__main__.py +18 -0
  71. src/mcp/agent_profiles.py +408 -0
  72. src/mcp/flow_agent_profiles.py +424 -0
  73. src/mcp/mcp_server.py +4086 -0
  74. src/mcp/rules_engine.py +487 -0
  75. src/mcp/runbook_manager.py +264 -0
  76. src/orchestrator/__init__.py +11 -0
  77. src/orchestrator/incident_workflow.py +244 -0
  78. src/orchestrator/tools_case.py +1085 -0
  79. src/orchestrator/tools_cti.py +359 -0
  80. src/orchestrator/tools_edr.py +315 -0
  81. src/orchestrator/tools_eng.py +378 -0
  82. src/orchestrator/tools_kb.py +156 -0
  83. src/orchestrator/tools_siem.py +1709 -0
  84. src/web/__init__.py +8 -0
  85. src/web/config_server.py +511 -0
@@ -0,0 +1,193 @@
1
+ """
2
+ TheHive implementation of the generic ``CaseManagementClient`` interface.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import List, Optional
8
+
9
+ from ....api.case_management import (
10
+ Case,
11
+ CaseAssignment,
12
+ CaseComment,
13
+ CaseManagementClient,
14
+ CaseObservable,
15
+ CaseSearchQuery,
16
+ CaseStatus,
17
+ CaseSummary,
18
+ )
19
+ from ....core.config import SamiConfig
20
+ from ....core.errors import IntegrationError
21
+ from ....core.logging import get_logger
22
+ from .thehive_http import TheHiveHttpClient
23
+ from .thehive_mapper import (
24
+ case_to_thehive_payload,
25
+ comment_to_thehive_payload,
26
+ observable_to_thehive_payload,
27
+ status_to_thehive_status,
28
+ thehive_case_to_generic,
29
+ thehive_case_to_summary,
30
+ thehive_comment_to_generic,
31
+ )
32
+
33
+
34
+ logger = get_logger("sami.integrations.thehive.client")
35
+
36
+
37
+ class TheHiveCaseManagementClient(CaseManagementClient):
38
+ """
39
+ Case management client backed by TheHive.
40
+ """
41
+
42
+ def __init__(self, http_client: TheHiveHttpClient) -> None:
43
+ self._http = http_client
44
+
45
+ @classmethod
46
+ def from_config(cls, config: SamiConfig) -> "TheHiveCaseManagementClient":
47
+ """
48
+ Factory to construct a client from ``SamiConfig``.
49
+ """
50
+
51
+ if not config.thehive:
52
+ raise IntegrationError("TheHive configuration is not set in SamiConfig")
53
+
54
+ http_client = TheHiveHttpClient(
55
+ base_url=config.thehive.base_url,
56
+ api_key=config.thehive.api_key,
57
+ timeout_seconds=config.thehive.timeout_seconds,
58
+ )
59
+ return cls(http_client=http_client)
60
+
61
+ # Core CRUD operations
62
+
63
+ def create_case(self, case: Case) -> Case:
64
+ payload = case_to_thehive_payload(case)
65
+ raw = self._http.post("/api/case", json=payload)
66
+ return thehive_case_to_generic(raw)
67
+
68
+ def get_case(self, case_id: str) -> Case:
69
+ raw = self._http.get(f"/api/case/{case_id}")
70
+ return thehive_case_to_generic(raw)
71
+
72
+ def list_cases(
73
+ self,
74
+ status: Optional[CaseStatus] = None,
75
+ limit: int = 50,
76
+ ) -> List[CaseSummary]:
77
+ params = {"range": f"[0,{max(limit - 1, 0)}]"}
78
+ if status is not None:
79
+ params["status"] = status_to_thehive_status(status).value
80
+ raw_list = self._http.get("/api/case", params=params)
81
+ return [thehive_case_to_summary(raw) for raw in raw_list]
82
+
83
+ def search_cases(self, query: CaseSearchQuery) -> List[CaseSummary]:
84
+ # Very simple text/field search mapping; real implementations may need
85
+ # to use TheHive's /api/case/_search endpoint with a JSON query body.
86
+ params: dict = {}
87
+ if query.text:
88
+ params["title"] = query.text
89
+ if query.status:
90
+ params["status"] = status_to_thehive_status(query.status).value
91
+
92
+ raw_list = self._http.get("/api/case", params=params)
93
+ summaries = [thehive_case_to_summary(raw) for raw in raw_list]
94
+ return summaries[: query.limit]
95
+
96
+ def update_case(self, case_id: str, updates: dict) -> Case:
97
+ raw = self._http.patch(f"/api/case/{case_id}", json=updates)
98
+ return thehive_case_to_generic(raw)
99
+
100
+ def delete_case(self, case_id: str) -> None:
101
+ self._http.delete(f"/api/case/{case_id}")
102
+
103
+ # Comments and observables
104
+
105
+ def add_case_comment(
106
+ self,
107
+ case_id: str,
108
+ content: str,
109
+ author: Optional[str] = None,
110
+ ) -> CaseComment:
111
+ comment = CaseComment(
112
+ id=None,
113
+ case_id=case_id,
114
+ author=author,
115
+ content=content,
116
+ )
117
+ payload = comment_to_thehive_payload(comment)
118
+ raw = self._http.post(f"/api/case/{case_id}/comment", json=payload)
119
+ return thehive_comment_to_generic(raw, case_id=case_id)
120
+
121
+ def add_case_observable(
122
+ self,
123
+ case_id: str,
124
+ observable: CaseObservable,
125
+ ) -> CaseObservable:
126
+ payload = observable_to_thehive_payload(observable)
127
+ raw = self._http.post(f"/api/case/{case_id}/artifact", json=payload)
128
+ from .thehive_models import parse_thehive_observable
129
+
130
+ return CaseObservable(
131
+ type=parse_thehive_observable(raw).data_type,
132
+ value=parse_thehive_observable(raw).data,
133
+ tags=parse_thehive_observable(raw).tags or [],
134
+ description=None,
135
+ )
136
+
137
+ # Status and assignment
138
+
139
+ def update_case_status(
140
+ self,
141
+ case_id: str,
142
+ status: CaseStatus,
143
+ ) -> Case:
144
+ payload = {"status": status_to_thehive_status(status).value}
145
+ raw = self._http.patch(f"/api/case/{case_id}", json=payload)
146
+ return thehive_case_to_generic(raw)
147
+
148
+ def assign_case(
149
+ self,
150
+ case_id: str,
151
+ assignee: str,
152
+ ) -> CaseAssignment:
153
+ payload = {"owner": assignee}
154
+ raw = self._http.patch(f"/api/case/{case_id}", json=payload)
155
+ case = thehive_case_to_generic(raw)
156
+ from datetime import datetime
157
+
158
+ return CaseAssignment(
159
+ case_id=case.id or case_id,
160
+ assignee=case.assignee or assignee,
161
+ assigned_at=case.updated_at or datetime.utcnow(),
162
+ )
163
+
164
+ # Linking and timeline
165
+
166
+ def link_cases(
167
+ self,
168
+ source_case_id: str,
169
+ target_case_id: str,
170
+ link_type: str,
171
+ ) -> None:
172
+ payload = {
173
+ "caseId": target_case_id,
174
+ "nature": link_type,
175
+ }
176
+ self._http.post(f"/api/case/{source_case_id}/link", json=payload)
177
+
178
+ def get_case_timeline(self, case_id: str) -> List[CaseComment]:
179
+ # For now, use the comments endpoint as a simple timeline proxy.
180
+ raw_list = self._http.get(f"/api/case/{case_id}/comment")
181
+ return [thehive_comment_to_generic(raw, case_id=case_id) for raw in raw_list]
182
+
183
+ # Health check
184
+
185
+ def ping(self) -> bool:
186
+ try:
187
+ self._http.get("/api/health")
188
+ return True
189
+ except IntegrationError:
190
+ logger.exception("TheHive ping failed")
191
+ return False
192
+
193
+
@@ -0,0 +1,147 @@
1
+ """
2
+ Low-level HTTP client for TheHive.
3
+
4
+ This module is responsible for:
5
+ - authentication (API key header)
6
+ - building URLs
7
+ - making HTTP requests
8
+ - basic error handling
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, Dict, Optional
15
+
16
+ import requests
17
+
18
+ from ....core.errors import IntegrationError
19
+ from ....core.logging import get_logger
20
+
21
+
22
+ logger = get_logger("sami.integrations.thehive.http")
23
+
24
+
25
+ @dataclass
26
+ class TheHiveHttpClient:
27
+ """
28
+ Simple HTTP client for TheHive's REST API.
29
+ """
30
+
31
+ base_url: str
32
+ api_key: str
33
+ timeout_seconds: int = 30
34
+
35
+ def _headers(self) -> Dict[str, str]:
36
+ return {
37
+ "Authorization": f"Bearer {self.api_key}",
38
+ "Content-Type": "application/json",
39
+ }
40
+
41
+ def authenticate(self) -> None:
42
+ """
43
+ Placeholder for any explicit authentication logic.
44
+
45
+ TheHive typically uses API keys in headers, so there may be no
46
+ separate login step. This method exists to match the design doc
47
+ and as a hook for future changes.
48
+ """
49
+
50
+ logger.debug("TheHiveHttpClient.authenticate called (no-op for API key)")
51
+
52
+ def request(
53
+ self,
54
+ method: str,
55
+ path: str,
56
+ json: Optional[Dict[str, Any]] = None,
57
+ params: Optional[Dict[str, Any]] = None,
58
+ ) -> Dict[str, Any]:
59
+ """
60
+ Make an HTTP request to TheHive and return the parsed JSON body.
61
+ """
62
+
63
+ url = build_url(self.base_url, path)
64
+ logger.debug(
65
+ "TheHive HTTP request",
66
+ extra={"method": method, "url": url, "params": params},
67
+ )
68
+
69
+ try:
70
+ response = requests.request(
71
+ method=method.upper(),
72
+ url=url,
73
+ headers=self._headers(),
74
+ json=json,
75
+ params=params,
76
+ timeout=self.timeout_seconds,
77
+ )
78
+ except requests.RequestException as exc:
79
+ raise IntegrationError(f"TheHive request failed: {exc}") from exc
80
+
81
+ handle_thehive_error(response)
82
+
83
+ try:
84
+ return response.json()
85
+ except ValueError as exc:
86
+ raise IntegrationError(
87
+ f"TheHive response did not contain valid JSON (status={response.status_code})"
88
+ ) from exc
89
+
90
+ def get(
91
+ self,
92
+ path: str,
93
+ params: Optional[Dict[str, Any]] = None,
94
+ ) -> Dict[str, Any]:
95
+ return self.request("GET", path, params=params)
96
+
97
+ def post(
98
+ self,
99
+ path: str,
100
+ json: Dict[str, Any],
101
+ ) -> Dict[str, Any]:
102
+ return self.request("POST", path, json=json)
103
+
104
+ def patch(
105
+ self,
106
+ path: str,
107
+ json: Dict[str, Any],
108
+ ) -> Dict[str, Any]:
109
+ return self.request("PATCH", path, json=json)
110
+
111
+ def delete(self, path: str) -> None:
112
+ self.request("DELETE", path)
113
+
114
+
115
+ def build_url(base_url: str, path: str) -> str:
116
+ """
117
+ Join base URL and path safely.
118
+ """
119
+
120
+ return f"{base_url.rstrip('/')}/{path.lstrip('/')}"
121
+
122
+
123
+ def handle_thehive_error(response: requests.Response) -> None:
124
+ """
125
+ Raise an IntegrationError for non-success responses from TheHive.
126
+ """
127
+
128
+ if 200 <= response.status_code < 300:
129
+ return
130
+
131
+ try:
132
+ payload = response.json()
133
+ except ValueError:
134
+ payload = {"raw": response.text}
135
+
136
+ logger.error(
137
+ "TheHive HTTP error",
138
+ extra={
139
+ "status_code": response.status_code,
140
+ "payload": payload,
141
+ },
142
+ )
143
+ raise IntegrationError(
144
+ f"TheHive error {response.status_code}: {payload}"
145
+ )
146
+
147
+
@@ -0,0 +1,190 @@
1
+ """
2
+ Mapping logic between generic case management DTOs and TheHive models/payloads.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, Dict, List
8
+
9
+ from ....api.case_management import (
10
+ Case,
11
+ CaseAssignment,
12
+ CaseComment,
13
+ CaseObservable,
14
+ CasePriority,
15
+ CaseStatus,
16
+ CaseSummary,
17
+ )
18
+ from .thehive_models import (
19
+ TheHiveCase,
20
+ TheHiveCasePriority,
21
+ TheHiveCaseStatus,
22
+ parse_thehive_case,
23
+ parse_thehive_observable,
24
+ )
25
+
26
+
27
+ # Generic → TheHive
28
+
29
+
30
+ def case_to_thehive_payload(case: Case) -> Dict[str, Any]:
31
+ """
32
+ Convert a generic ``Case`` into a TheHive case creation/update payload.
33
+ """
34
+
35
+ payload: Dict[str, Any] = {
36
+ "title": case.title,
37
+ "description": case.description,
38
+ "tags": case.tags or [],
39
+ }
40
+
41
+ if case.priority is not None:
42
+ payload["severity"] = {
43
+ CasePriority.LOW: 1,
44
+ CasePriority.MEDIUM: 2,
45
+ CasePriority.HIGH: 3,
46
+ CasePriority.CRITICAL: 4,
47
+ }[case.priority]
48
+
49
+ if case.status is not None:
50
+ payload["status"] = status_to_thehive_status(case.status).value
51
+
52
+ if case.assignee:
53
+ payload["owner"] = case.assignee
54
+
55
+ return payload
56
+
57
+
58
+ def comment_to_thehive_payload(comment: CaseComment) -> Dict[str, Any]:
59
+ return {
60
+ "message": comment.content,
61
+ }
62
+
63
+
64
+ def observable_to_thehive_payload(observable: CaseObservable) -> Dict[str, Any]:
65
+ return {
66
+ "dataType": observable.type,
67
+ "data": observable.value,
68
+ "tags": observable.tags or [],
69
+ "message": observable.description or "",
70
+ }
71
+
72
+
73
+ def status_to_thehive_status(status: CaseStatus) -> TheHiveCaseStatus:
74
+ mapping = {
75
+ CaseStatus.OPEN: TheHiveCaseStatus.OPEN,
76
+ CaseStatus.IN_PROGRESS: TheHiveCaseStatus.IN_PROGRESS,
77
+ CaseStatus.CLOSED: TheHiveCaseStatus.RESOLVED,
78
+ }
79
+ return mapping.get(status, TheHiveCaseStatus.OPEN)
80
+
81
+
82
+ def priority_to_thehive_priority(priority: CasePriority) -> TheHiveCasePriority:
83
+ mapping = {
84
+ CasePriority.LOW: TheHiveCasePriority.LOW,
85
+ CasePriority.MEDIUM: TheHiveCasePriority.MEDIUM,
86
+ CasePriority.HIGH: TheHiveCasePriority.HIGH,
87
+ CasePriority.CRITICAL: TheHiveCasePriority.CRITICAL,
88
+ }
89
+ return mapping[priority]
90
+
91
+
92
+ # TheHive → Generic
93
+
94
+
95
+ def thehive_case_to_generic(raw: Dict[str, Any]) -> Case:
96
+ hive_case: TheHiveCase = parse_thehive_case(raw)
97
+
98
+ # Map severity back to CasePriority using simple thresholds.
99
+ severity = hive_case.severity or 0
100
+ if severity <= 1:
101
+ priority = CasePriority.LOW
102
+ elif severity == 2:
103
+ priority = CasePriority.MEDIUM
104
+ elif severity == 3:
105
+ priority = CasePriority.HIGH
106
+ else:
107
+ priority = CasePriority.CRITICAL
108
+
109
+ status_mapping = {
110
+ TheHiveCaseStatus.OPEN: CaseStatus.OPEN,
111
+ TheHiveCaseStatus.IN_PROGRESS: CaseStatus.IN_PROGRESS,
112
+ TheHiveCaseStatus.RESOLVED: CaseStatus.CLOSED,
113
+ TheHiveCaseStatus.DELETED: CaseStatus.CLOSED,
114
+ }
115
+ status = status_mapping.get(hive_case.status, CaseStatus.OPEN)
116
+
117
+ return Case(
118
+ id=hive_case.id,
119
+ title=hive_case.title,
120
+ description=hive_case.description or "",
121
+ status=status,
122
+ priority=priority,
123
+ created_at=hive_case.start_date,
124
+ updated_at=hive_case.start_date,
125
+ assignee=hive_case.owner,
126
+ tags=hive_case.tags or [],
127
+ observables=None,
128
+ )
129
+
130
+
131
+ def thehive_case_to_summary(raw: Dict[str, Any]) -> CaseSummary:
132
+ hive_case: TheHiveCase = parse_thehive_case(raw)
133
+
134
+ severity = hive_case.severity or 0
135
+ if severity <= 1:
136
+ priority = CasePriority.LOW
137
+ elif severity == 2:
138
+ priority = CasePriority.MEDIUM
139
+ elif severity == 3:
140
+ priority = CasePriority.HIGH
141
+ else:
142
+ priority = CasePriority.CRITICAL
143
+
144
+ status_mapping = {
145
+ TheHiveCaseStatus.OPEN: CaseStatus.OPEN,
146
+ TheHiveCaseStatus.IN_PROGRESS: CaseStatus.IN_PROGRESS,
147
+ TheHiveCaseStatus.RESOLVED: CaseStatus.CLOSED,
148
+ TheHiveCaseStatus.DELETED: CaseStatus.CLOSED,
149
+ }
150
+ status = status_mapping.get(hive_case.status, CaseStatus.OPEN)
151
+
152
+ return CaseSummary(
153
+ id=hive_case.id,
154
+ title=hive_case.title,
155
+ status=status,
156
+ priority=priority,
157
+ created_at=hive_case.start_date,
158
+ updated_at=hive_case.start_date,
159
+ assignee=hive_case.owner,
160
+ )
161
+
162
+
163
+ def thehive_comment_to_generic(raw: Dict[str, Any], case_id: str) -> CaseComment:
164
+ # TheHive uses "message" and "createdAt" in many comment-like resources.
165
+ from datetime import datetime
166
+
167
+ created_at_value = raw.get("createdAt")
168
+ created_at = None
169
+ if isinstance(created_at_value, (int, float)):
170
+ created_at = datetime.fromtimestamp(created_at_value / 1000.0)
171
+
172
+ return CaseComment(
173
+ id=str(raw.get("id") or raw.get("_id")),
174
+ case_id=case_id,
175
+ author=raw.get("user"),
176
+ content=raw.get("message", ""),
177
+ created_at=created_at,
178
+ )
179
+
180
+
181
+ def thehive_observable_to_generic(raw: Dict[str, Any], case_id: str) -> CaseObservable:
182
+ hive_obs = parse_thehive_observable(raw)
183
+ return CaseObservable(
184
+ type=hive_obs.data_type,
185
+ value=hive_obs.data,
186
+ tags=hive_obs.tags or [],
187
+ description=None,
188
+ )
189
+
190
+
@@ -0,0 +1,125 @@
1
+ """
2
+ TheHive-specific data models.
3
+
4
+ These dataclasses are close to TheHive's API payloads but kept separate
5
+ from the generic DTOs defined under ``src/api``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from typing import Any, Dict, List, Optional
14
+
15
+
16
+ class TheHiveCaseStatus(str, Enum):
17
+ OPEN = "Open"
18
+ IN_PROGRESS = "InProgress"
19
+ RESOLVED = "Resolved"
20
+ DELETED = "Deleted"
21
+
22
+
23
+ class TheHiveCasePriority(str, Enum):
24
+ LOW = "Low"
25
+ MEDIUM = "Medium"
26
+ HIGH = "High"
27
+ CRITICAL = "Critical"
28
+
29
+
30
+ @dataclass
31
+ class TheHiveUser:
32
+ id: str
33
+ login: str
34
+ name: Optional[str] = None
35
+
36
+
37
+ @dataclass
38
+ class TheHiveObservable:
39
+ id: str
40
+ data_type: str
41
+ data: str
42
+ tlp: Optional[int] = None
43
+ tags: Optional[List[str]] = None
44
+
45
+
46
+ @dataclass
47
+ class TheHiveAlert:
48
+ id: str
49
+ title: str
50
+ description: Optional[str] = None
51
+ severity: Optional[int] = None
52
+ source: Optional[str] = None
53
+ source_ref: Optional[str] = None
54
+
55
+
56
+ @dataclass
57
+ class TheHiveCase:
58
+ id: str
59
+ title: str
60
+ description: Optional[str]
61
+ severity: Optional[int]
62
+ start_date: Optional[datetime]
63
+ status: TheHiveCaseStatus
64
+ owner: Optional[str]
65
+ tags: Optional[List[str]] = None
66
+
67
+
68
+ def parse_thehive_case(raw: Dict[str, Any]) -> TheHiveCase:
69
+ """
70
+ Parse a raw TheHive case dict into a ``TheHiveCase`` instance.
71
+ """
72
+
73
+ # TheHive uses numeric timestamps (epoch ms); keep parsing simple and
74
+ # allow None if absent.
75
+ start_date_value = raw.get("startDate")
76
+ start_date: Optional[datetime]
77
+ if isinstance(start_date_value, (int, float)):
78
+ start_date = datetime.fromtimestamp(start_date_value / 1000.0)
79
+ else:
80
+ start_date = None
81
+
82
+ status = raw.get("status", "Open")
83
+ case_status = TheHiveCaseStatus(status) if status in TheHiveCaseStatus._value2member_map_ else TheHiveCaseStatus.OPEN
84
+
85
+ return TheHiveCase(
86
+ id=str(raw.get("id") or raw.get("_id")),
87
+ title=raw.get("title", ""),
88
+ description=raw.get("description"),
89
+ severity=raw.get("severity"),
90
+ start_date=start_date,
91
+ status=case_status,
92
+ owner=raw.get("owner"),
93
+ tags=raw.get("tags") or [],
94
+ )
95
+
96
+
97
+ def parse_thehive_observable(raw: Dict[str, Any]) -> TheHiveObservable:
98
+ """
99
+ Parse a raw TheHive observable dict.
100
+ """
101
+
102
+ return TheHiveObservable(
103
+ id=str(raw.get("id") or raw.get("_id")),
104
+ data_type=raw.get("dataType", ""),
105
+ data=raw.get("data", ""),
106
+ tlp=raw.get("tlp"),
107
+ tags=raw.get("tags") or [],
108
+ )
109
+
110
+
111
+ def parse_thehive_alert(raw: Dict[str, Any]) -> TheHiveAlert:
112
+ """
113
+ Parse a raw TheHive alert dict.
114
+ """
115
+
116
+ return TheHiveAlert(
117
+ id=str(raw.get("id") or raw.get("_id")),
118
+ title=raw.get("title", ""),
119
+ description=raw.get("description"),
120
+ severity=raw.get("severity"),
121
+ source=raw.get("source"),
122
+ source_ref=raw.get("sourceRef"),
123
+ )
124
+
125
+
@@ -0,0 +1,6 @@
1
+ """
2
+ CTI (Cyber Threat Intelligence) integrations.
3
+
4
+ This package provides integrations with threat intelligence platforms.
5
+ """
6
+
@@ -0,0 +1,10 @@
1
+ """
2
+ Local TIP (Threat Intelligence Platform) integration.
3
+
4
+ This module provides integration with a local TIP for hash lookups.
5
+ """
6
+
7
+ from .local_tip_client import LocalTipCTIClient
8
+
9
+ __all__ = ["LocalTipCTIClient"]
10
+