binalyze-air-sdk 1.0.1__py3-none-any.whl → 1.0.3__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 (142) hide show
  1. binalyze_air/__init__.py +77 -77
  2. binalyze_air/apis/__init__.py +67 -27
  3. binalyze_air/apis/acquisitions.py +107 -0
  4. binalyze_air/apis/api_tokens.py +49 -0
  5. binalyze_air/apis/assets.py +161 -0
  6. binalyze_air/apis/audit_logs.py +26 -0
  7. binalyze_air/apis/{authentication.py → auth.py} +29 -27
  8. binalyze_air/apis/auto_asset_tags.py +79 -75
  9. binalyze_air/apis/backup.py +177 -0
  10. binalyze_air/apis/baseline.py +46 -0
  11. binalyze_air/apis/cases.py +225 -0
  12. binalyze_air/apis/cloud_forensics.py +116 -0
  13. binalyze_air/apis/event_subscription.py +96 -96
  14. binalyze_air/apis/evidence.py +249 -53
  15. binalyze_air/apis/interact.py +153 -36
  16. binalyze_air/apis/investigation_hub.py +234 -0
  17. binalyze_air/apis/license.py +104 -0
  18. binalyze_air/apis/logger.py +83 -0
  19. binalyze_air/apis/multipart_upload.py +201 -0
  20. binalyze_air/apis/notifications.py +115 -0
  21. binalyze_air/apis/organizations.py +267 -0
  22. binalyze_air/apis/params.py +44 -39
  23. binalyze_air/apis/policies.py +186 -0
  24. binalyze_air/apis/preset_filters.py +79 -0
  25. binalyze_air/apis/recent_activities.py +71 -0
  26. binalyze_air/apis/relay_server.py +104 -0
  27. binalyze_air/apis/settings.py +395 -27
  28. binalyze_air/apis/tasks.py +80 -0
  29. binalyze_air/apis/triage.py +197 -0
  30. binalyze_air/apis/user_management.py +183 -74
  31. binalyze_air/apis/webhook_executions.py +50 -0
  32. binalyze_air/apis/webhooks.py +322 -230
  33. binalyze_air/base.py +207 -133
  34. binalyze_air/client.py +217 -1337
  35. binalyze_air/commands/__init__.py +175 -145
  36. binalyze_air/commands/acquisitions.py +661 -387
  37. binalyze_air/commands/api_tokens.py +55 -0
  38. binalyze_air/commands/assets.py +324 -362
  39. binalyze_air/commands/{authentication.py → auth.py} +36 -36
  40. binalyze_air/commands/auto_asset_tags.py +230 -230
  41. binalyze_air/commands/backup.py +47 -0
  42. binalyze_air/commands/baseline.py +32 -396
  43. binalyze_air/commands/cases.py +609 -602
  44. binalyze_air/commands/cloud_forensics.py +88 -0
  45. binalyze_air/commands/event_subscription.py +101 -101
  46. binalyze_air/commands/evidences.py +918 -988
  47. binalyze_air/commands/interact.py +172 -58
  48. binalyze_air/commands/investigation_hub.py +315 -0
  49. binalyze_air/commands/license.py +183 -0
  50. binalyze_air/commands/logger.py +126 -0
  51. binalyze_air/commands/multipart_upload.py +363 -0
  52. binalyze_air/commands/notifications.py +45 -0
  53. binalyze_air/commands/organizations.py +200 -221
  54. binalyze_air/commands/policies.py +175 -203
  55. binalyze_air/commands/preset_filters.py +55 -0
  56. binalyze_air/commands/recent_activities.py +32 -0
  57. binalyze_air/commands/relay_server.py +144 -0
  58. binalyze_air/commands/settings.py +431 -29
  59. binalyze_air/commands/tasks.py +95 -56
  60. binalyze_air/commands/triage.py +224 -360
  61. binalyze_air/commands/user_management.py +351 -126
  62. binalyze_air/commands/webhook_executions.py +77 -0
  63. binalyze_air/config.py +244 -244
  64. binalyze_air/exceptions.py +49 -49
  65. binalyze_air/http_client.py +426 -305
  66. binalyze_air/models/__init__.py +287 -285
  67. binalyze_air/models/acquisitions.py +365 -250
  68. binalyze_air/models/api_tokens.py +73 -0
  69. binalyze_air/models/assets.py +438 -438
  70. binalyze_air/models/audit.py +247 -272
  71. binalyze_air/models/audit_logs.py +14 -0
  72. binalyze_air/models/{authentication.py → auth.py} +69 -69
  73. binalyze_air/models/auto_asset_tags.py +227 -116
  74. binalyze_air/models/backup.py +138 -0
  75. binalyze_air/models/baseline.py +231 -231
  76. binalyze_air/models/cases.py +275 -275
  77. binalyze_air/models/cloud_forensics.py +145 -0
  78. binalyze_air/models/event_subscription.py +170 -171
  79. binalyze_air/models/evidence.py +65 -65
  80. binalyze_air/models/evidences.py +367 -348
  81. binalyze_air/models/interact.py +266 -135
  82. binalyze_air/models/investigation_hub.py +265 -0
  83. binalyze_air/models/license.py +150 -0
  84. binalyze_air/models/logger.py +83 -0
  85. binalyze_air/models/multipart_upload.py +352 -0
  86. binalyze_air/models/notifications.py +138 -0
  87. binalyze_air/models/organizations.py +293 -293
  88. binalyze_air/models/params.py +153 -127
  89. binalyze_air/models/policies.py +260 -249
  90. binalyze_air/models/preset_filters.py +79 -0
  91. binalyze_air/models/recent_activities.py +70 -0
  92. binalyze_air/models/relay_server.py +121 -0
  93. binalyze_air/models/settings.py +538 -84
  94. binalyze_air/models/tasks.py +215 -149
  95. binalyze_air/models/triage.py +141 -142
  96. binalyze_air/models/user_management.py +200 -97
  97. binalyze_air/models/webhook_executions.py +33 -0
  98. binalyze_air/queries/__init__.py +121 -133
  99. binalyze_air/queries/acquisitions.py +155 -155
  100. binalyze_air/queries/api_tokens.py +46 -0
  101. binalyze_air/queries/assets.py +186 -105
  102. binalyze_air/queries/audit.py +400 -416
  103. binalyze_air/queries/{authentication.py → auth.py} +55 -55
  104. binalyze_air/queries/auto_asset_tags.py +59 -59
  105. binalyze_air/queries/backup.py +66 -0
  106. binalyze_air/queries/baseline.py +21 -185
  107. binalyze_air/queries/cases.py +292 -292
  108. binalyze_air/queries/cloud_forensics.py +137 -0
  109. binalyze_air/queries/event_subscription.py +54 -54
  110. binalyze_air/queries/evidence.py +139 -139
  111. binalyze_air/queries/evidences.py +279 -279
  112. binalyze_air/queries/interact.py +140 -28
  113. binalyze_air/queries/investigation_hub.py +329 -0
  114. binalyze_air/queries/license.py +85 -0
  115. binalyze_air/queries/logger.py +58 -0
  116. binalyze_air/queries/multipart_upload.py +180 -0
  117. binalyze_air/queries/notifications.py +71 -0
  118. binalyze_air/queries/organizations.py +222 -222
  119. binalyze_air/queries/params.py +154 -115
  120. binalyze_air/queries/policies.py +149 -149
  121. binalyze_air/queries/preset_filters.py +60 -0
  122. binalyze_air/queries/recent_activities.py +44 -0
  123. binalyze_air/queries/relay_server.py +42 -0
  124. binalyze_air/queries/settings.py +533 -20
  125. binalyze_air/queries/tasks.py +125 -81
  126. binalyze_air/queries/triage.py +230 -230
  127. binalyze_air/queries/user_management.py +193 -83
  128. binalyze_air/queries/webhook_executions.py +39 -0
  129. binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
  130. binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
  131. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
  132. binalyze_air/apis/endpoints.py +0 -22
  133. binalyze_air/apis/evidences.py +0 -216
  134. binalyze_air/apis/users.py +0 -68
  135. binalyze_air/commands/users.py +0 -101
  136. binalyze_air/models/endpoints.py +0 -76
  137. binalyze_air/models/users.py +0 -82
  138. binalyze_air/queries/endpoints.py +0 -25
  139. binalyze_air/queries/users.py +0 -69
  140. binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
  141. binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
  142. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
binalyze_air/client.py CHANGED
@@ -1,1338 +1,218 @@
1
- """
2
- Main client for the Binalyze AIR SDK using CQRS architecture.
3
- """
4
-
5
- import os
6
- from typing import List, Optional, Union, Dict, Any
7
- from datetime import datetime
8
-
9
- from .config import AIRConfig
10
- from .http_client import HTTPClient
11
-
12
- # Import models
13
- from .models.assets import Asset, AssetDetail, AssetTask, AssetFilter, AssetTaskFilter
14
- from .models.cases import (
15
- Case, CaseActivity, CaseEndpoint, CaseTask, User, CaseFilter, CaseActivityFilter,
16
- CreateCaseRequest, UpdateCaseRequest, CaseNote, CaseEndpointFilter, CaseTaskFilter, CaseUserFilter
17
- )
18
- from .models.tasks import Task, TaskFilter, TaskAssignment
19
- from .models.acquisitions import (
20
- AcquisitionProfile, AcquisitionProfileDetails, AcquisitionFilter,
21
- AcquisitionTaskRequest, ImageAcquisitionTaskRequest, CreateAcquisitionProfileRequest
22
- )
23
- from .models.policies import (
24
- Policy, PolicyAssignment, PolicyExecution, PolicyFilter,
25
- CreatePolicyRequest, UpdatePolicyRequest, AssignPolicyRequest
26
- )
27
- from .models.organizations import (
28
- Organization, OrganizationUser, OrganizationRole, OrganizationLicense,
29
- OrganizationSettings, OrganizationFilter, CreateOrganizationRequest,
30
- UpdateOrganizationRequest, AddUserToOrganizationRequest, OrganizationsPaginatedResponse,
31
- OrganizationUsersPaginatedResponse
32
- )
33
- from .models.triage import (
34
- TriageRule, TriageTag,
35
- TriageFilter, CreateTriageRuleRequest, UpdateTriageRuleRequest,
36
- CreateTriageTagRequest, CreateTriageProfileRequest
37
- )
38
- from .models.audit import (
39
- AuditLog, AuditSummary, AuditUserActivity, AuditSystemEvent,
40
- AuditRetentionPolicy, AuditFilter, AuditLogsFilter, AuditLevel
41
- )
42
- from .models.baseline import (
43
- Baseline, BaselineProfile, BaselineComparison, BaselineSchedule,
44
- BaselineFilter, CreateBaselineRequest, UpdateBaselineRequest,
45
- CreateBaselineProfileRequest, CompareBaselineRequest
46
- )
47
- from .models.authentication import (
48
- AuthStatus, LoginRequest, LoginResponse
49
- )
50
- from .models.user_management import (
51
- UserManagementUser, CreateUserRequest, UpdateUserRequest,
52
- AIUser, CreateAIUserRequest, APIUser, CreateAPIUserRequest, UserFilter
53
- )
54
- from .models.evidence import (
55
- EvidencePPC, EvidenceReportFileInfo, EvidenceReport
56
- )
57
- from .models.auto_asset_tags import (
58
- AutoAssetTag, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
59
- StartTaggingRequest, TaggingResult, AutoAssetTagFilter
60
- )
61
- from .models.evidences import (
62
- EvidenceRepository, AmazonS3Repository, AzureStorageRepository,
63
- FTPSRepository, SFTPRepository, SMBRepository, RepositoryFilter,
64
- CreateAmazonS3RepositoryRequest, UpdateAmazonS3RepositoryRequest,
65
- CreateAzureStorageRepositoryRequest, UpdateAzureStorageRepositoryRequest,
66
- CreateFTPSRepositoryRequest, UpdateFTPSRepositoryRequest,
67
- CreateSFTPRepositoryRequest, UpdateSFTPRepositoryRequest,
68
- CreateSMBRepositoryRequest, UpdateSMBRepositoryRequest,
69
- ValidateRepositoryRequest, ValidationResult
70
- )
71
- from .models.event_subscription import (
72
- EventSubscription, EventSubscriptionFilter, CreateEventSubscriptionRequest,
73
- UpdateEventSubscriptionRequest
74
- )
75
- from .models.interact import (
76
- ShellInteraction, AssignShellTaskRequest, ShellTaskResponse
77
- )
78
- from .models.params import (
79
- AcquisitionArtifact, EDiscoveryPattern, AcquisitionEvidence, DroneAnalyzer
80
- )
81
- from .models.settings import (
82
- BannerSettings, UpdateBannerSettingsRequest
83
- )
84
- from .models.endpoints import (
85
- EndpointTag, EndpointTagFilter
86
- )
87
-
88
- # Import queries
89
- from .queries.assets import (
90
- ListAssetsQuery,
91
- GetAssetQuery,
92
- GetAssetTasksQuery,
93
- )
94
- from .queries.cases import (
95
- ListCasesQuery,
96
- GetCaseQuery,
97
- GetCaseActivitiesQuery,
98
- GetCaseEndpointsQuery,
99
- GetCaseTasksQuery,
100
- GetCaseUsersQuery,
101
- CheckCaseNameQuery,
102
- )
103
- from .queries.tasks import (
104
- ListTasksQuery,
105
- GetTaskQuery,
106
- GetTaskAssignmentsQuery,
107
- )
108
- from .queries.acquisitions import (
109
- ListAcquisitionProfilesQuery,
110
- GetAcquisitionProfileQuery,
111
- )
112
-
113
- # Import commands
114
- from .commands.assets import (
115
- IsolateAssetsCommand,
116
- UnisolateAssetsCommand,
117
- RebootAssetsCommand,
118
- ShutdownAssetsCommand,
119
- AddTagsToAssetsCommand,
120
- RemoveTagsFromAssetsCommand,
121
- UninstallAssetsCommand,
122
- LogRetrievalCommand,
123
- VersionUpdateCommand,
124
- )
125
- from .commands.cases import (
126
- CreateCaseCommand,
127
- UpdateCaseCommand,
128
- CloseCaseCommand,
129
- OpenCaseCommand,
130
- ArchiveCaseCommand,
131
- ChangeCaseOwnerCommand,
132
- RemoveEndpointsFromCaseCommand,
133
- RemoveTaskAssignmentFromCaseCommand,
134
- ImportTaskAssignmentsToCaseCommand,
135
- AddNoteToCaseCommand,
136
- UpdateNoteToCaseCommand,
137
- DeleteNoteToCaseCommand,
138
- ExportCaseNotesCommand,
139
- ExportCasesCommand,
140
- ExportCaseEndpointsCommand,
141
- ExportCaseActivitiesCommand,
142
- )
143
- from .commands.tasks import (
144
- CancelTaskCommand,
145
- CancelTaskAssignmentCommand,
146
- DeleteTaskAssignmentCommand,
147
- DeleteTaskCommand,
148
- )
149
- from .commands.acquisitions import (
150
- CreateAcquisitionCommand,
151
- CreateImageAcquisitionCommand,
152
- CreateAcquisitionProfileCommand,
153
- AssignAcquisitionTaskCommand,
154
- AssignImageAcquisitionTaskCommand,
155
- )
156
-
157
- # Import API classes
158
- from .apis.event_subscription import EventSubscriptionAPI
159
- from .apis.interact import InteractAPI
160
- from .apis.params import ParamsAPI
161
- from .apis.settings import SettingsAPI
162
- from .apis.endpoints import EndpointsAPI
163
- from .apis.evidences import EvidencesAPI
164
- from .apis.authentication import AuthenticationAPI
165
- from .apis.users import UsersAPI
166
- from .apis.evidence import EvidenceAPI
167
- from .apis.auto_asset_tags import AutoAssetTagsAPI
168
- from .apis.webhooks import WebhookAPI
169
-
170
-
171
- class AssetsAPI:
172
- """Assets API with CQRS pattern - separated queries and commands."""
173
-
174
- def __init__(self, http_client: HTTPClient):
175
- self.http_client = http_client
176
-
177
- # QUERIES (Read operations)
178
- def list(self, filter_params: Optional[AssetFilter] = None) -> List[Asset]:
179
- """List assets with optional filtering."""
180
- query = ListAssetsQuery(self.http_client, filter_params)
181
- return query.execute()
182
-
183
- def get(self, asset_id: str) -> AssetDetail:
184
- """Get a specific asset by ID."""
185
- query = GetAssetQuery(self.http_client, asset_id)
186
- return query.execute()
187
-
188
- def get_tasks(self, asset_id: str, filter_params: Optional[AssetTaskFilter] = None) -> List[AssetTask]:
189
- """Get tasks for a specific asset with optional filtering."""
190
- query = GetAssetTasksQuery(self.http_client, asset_id, filter_params)
191
- return query.execute()
192
-
193
- # COMMANDS (Write operations)
194
- def isolate(self, endpoint_ids: Union[str, List[str]], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
195
- """Isolate one or more assets."""
196
- # Create AssetFilter from endpoint IDs for backward compatibility
197
- from .commands.assets import create_asset_filter_from_endpoint_ids
198
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
199
- command = IsolateAssetsCommand(self.http_client, asset_filter)
200
- return command.execute()
201
-
202
- def unisolate(self, endpoint_ids: Union[str, List[str]], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
203
- """Remove isolation from one or more assets."""
204
- from .commands.assets import create_asset_filter_from_endpoint_ids
205
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
206
- command = UnisolateAssetsCommand(self.http_client, asset_filter)
207
- return command.execute()
208
-
209
- def reboot(self, endpoint_ids: Union[str, List[str]], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
210
- """Reboot one or more assets."""
211
- from .commands.assets import create_asset_filter_from_endpoint_ids
212
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
213
- command = RebootAssetsCommand(self.http_client, asset_filter)
214
- return command.execute()
215
-
216
- def shutdown(self, endpoint_ids: Union[str, List[str]], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
217
- """Shutdown one or more assets."""
218
- from .commands.assets import create_asset_filter_from_endpoint_ids
219
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
220
- command = ShutdownAssetsCommand(self.http_client, asset_filter)
221
- return command.execute()
222
-
223
- def add_tags(self, endpoint_ids: List[str], tags: List[str], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
224
- """Add tags to assets."""
225
- from .commands.assets import create_asset_filter_from_endpoint_ids
226
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
227
- command = AddTagsToAssetsCommand(self.http_client, asset_filter, tags)
228
- return command.execute()
229
-
230
- def remove_tags(self, endpoint_ids: List[str], tags: List[str], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
231
- """Remove tags from assets."""
232
- from .commands.assets import create_asset_filter_from_endpoint_ids
233
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
234
- command = RemoveTagsFromAssetsCommand(self.http_client, asset_filter, tags)
235
- return command.execute()
236
-
237
- def uninstall(self, endpoint_ids: List[str], purge_data: bool = False, organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
238
- """Uninstall assets with optional data purging."""
239
- from .commands.assets import create_asset_filter_from_endpoint_ids
240
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
241
- if purge_data:
242
- from .commands.assets import PurgeAndUninstallAssetsCommand
243
- command = PurgeAndUninstallAssetsCommand(self.http_client, asset_filter)
244
- else:
245
- from .commands.assets import UninstallAssetsCommand
246
- command = UninstallAssetsCommand(self.http_client, asset_filter)
247
- return command.execute()
248
-
249
- def retrieve_logs(self, endpoint_ids: List[str], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
250
- """Retrieve logs from assets."""
251
- from .commands.assets import create_asset_filter_from_endpoint_ids
252
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
253
- command = LogRetrievalCommand(self.http_client, asset_filter)
254
- return command.execute()
255
-
256
- def version_update(self, endpoint_ids: List[str], organization_ids: Optional[List[Union[int, str]]] = None) -> Dict[str, Any]:
257
- """Update version on assets."""
258
- from .commands.assets import create_asset_filter_from_endpoint_ids
259
- asset_filter = create_asset_filter_from_endpoint_ids(endpoint_ids, organization_ids)
260
- command = VersionUpdateCommand(self.http_client, asset_filter)
261
- return command.execute()
262
-
263
-
264
- class CasesAPI:
265
- """Cases API with CQRS pattern - separated queries and commands."""
266
-
267
- def __init__(self, http_client: HTTPClient):
268
- self.http_client = http_client
269
-
270
- # QUERIES (Read operations)
271
- def list(self, filter_params: Optional[CaseFilter] = None, organization_ids: Optional[List[int]] = None) -> List[Case]:
272
- """List cases with optional filtering."""
273
- query = ListCasesQuery(self.http_client, filter_params, organization_ids)
274
- return query.execute()
275
-
276
- def get(self, case_id: str) -> Case:
277
- """Get a specific case by ID."""
278
- query = GetCaseQuery(self.http_client, case_id)
279
- return query.execute()
280
-
281
- def get_activities(self, case_id: str, filter_params: Optional[CaseActivityFilter] = None) -> List[CaseActivity]:
282
- """Get activities for a specific case with optional filtering, pagination, and sorting."""
283
- query = GetCaseActivitiesQuery(self.http_client, case_id, filter_params)
284
- return query.execute()
285
-
286
- def get_endpoints(self, case_id: str, filter_params: Optional[CaseEndpointFilter] = None, organization_ids: Optional[List[int]] = None) -> List[CaseEndpoint]:
287
- """Get endpoints for a specific case with comprehensive filtering support.
288
-
289
- Args:
290
- case_id: The case ID to get endpoints for
291
- filter_params: Optional CaseEndpointFilter with comprehensive filtering options
292
- organization_ids: Optional list of organization IDs (for backward compatibility)
293
-
294
- Returns:
295
- List of CaseEndpoint objects
296
-
297
- Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
298
- """
299
- # Handle backward compatibility
300
- if filter_params is None:
301
- filter_params = CaseEndpointFilter()
302
-
303
- # If organization_ids is provided and not set in filter_params, use it
304
- if organization_ids is not None and filter_params.organization_ids is None:
305
- filter_params.organization_ids = organization_ids
306
-
307
- query = GetCaseEndpointsQuery(self.http_client, case_id, filter_params)
308
- return query.execute()
309
-
310
- def get_tasks(self, case_id: str, filter_params: Optional[CaseTaskFilter] = None, organization_ids: Optional[List[int]] = None) -> List[CaseTask]:
311
- """Get tasks for a specific case with comprehensive filtering support.
312
-
313
- Args:
314
- case_id: The case ID to get tasks for
315
- filter_params: Optional CaseTaskFilter with comprehensive filtering options
316
- organization_ids: Optional list of organization IDs (for backward compatibility)
317
-
318
- Returns:
319
- List of CaseTask objects
320
-
321
- Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
322
- """
323
- # Handle backward compatibility
324
- if filter_params is None:
325
- filter_params = CaseTaskFilter()
326
-
327
- # If organization_ids is provided and not set in filter_params, use it
328
- if organization_ids is not None and filter_params.organization_ids is None:
329
- filter_params.organization_ids = organization_ids
330
-
331
- query = GetCaseTasksQuery(self.http_client, case_id, filter_params)
332
- return query.execute()
333
-
334
- def get_users(self, case_id: str, filter_params: Optional[CaseUserFilter] = None, organization_ids: Optional[List[int]] = None) -> List[User]:
335
- """Get users for a specific case with comprehensive filtering support.
336
-
337
- Args:
338
- case_id: The case ID to get users for
339
- filter_params: Optional CaseUserFilter with comprehensive filtering options
340
- organization_ids: Optional list of organization IDs (for backward compatibility)
341
-
342
- Returns:
343
- List of User objects
344
-
345
- Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
346
- """
347
- # Handle backward compatibility
348
- if filter_params is None:
349
- filter_params = CaseUserFilter()
350
-
351
- # If organization_ids is provided and not set in filter_params, use it
352
- if organization_ids is not None and filter_params.organization_ids is None:
353
- filter_params.organization_ids = organization_ids
354
-
355
- query = GetCaseUsersQuery(self.http_client, case_id, filter_params)
356
- return query.execute()
357
-
358
- def check_name(self, name: str) -> bool:
359
- """Check if a case name is available."""
360
- query = CheckCaseNameQuery(self.http_client, name)
361
- return query.execute()
362
-
363
- # COMMANDS (Write operations)
364
- def create(self, case_data: CreateCaseRequest) -> Case:
365
- """Create a new case."""
366
- command = CreateCaseCommand(self.http_client, case_data)
367
- return command.execute()
368
-
369
- def update(self, case_id: str, update_data: UpdateCaseRequest) -> Case:
370
- """Update an existing case."""
371
- command = UpdateCaseCommand(self.http_client, case_id, update_data)
372
- return command.execute()
373
-
374
- def close(self, case_id: str) -> Case:
375
- """Close a case."""
376
- command = CloseCaseCommand(self.http_client, case_id)
377
- return command.execute()
378
-
379
- def open(self, case_id: str) -> Case:
380
- """Open a case."""
381
- command = OpenCaseCommand(self.http_client, case_id)
382
- return command.execute()
383
-
384
- def archive(self, case_id: str) -> Case:
385
- """Archive a case."""
386
- command = ArchiveCaseCommand(self.http_client, case_id)
387
- return command.execute()
388
-
389
- def change_owner(self, case_id: str, new_owner_id: str) -> Case:
390
- """Change case owner."""
391
- command = ChangeCaseOwnerCommand(self.http_client, case_id, new_owner_id)
392
- return command.execute()
393
-
394
- def remove_endpoints(self, case_id: str, filter_params: AssetFilter) -> Dict[str, Any]:
395
- """Remove endpoints from a case."""
396
- command = RemoveEndpointsFromCaseCommand(self.http_client, case_id, filter_params)
397
- return command.execute()
398
-
399
- def remove_task_assignment(self, case_id: str, task_assignment_id: str) -> Dict[str, Any]:
400
- """Remove task assignment from a case."""
401
- command = RemoveTaskAssignmentFromCaseCommand(self.http_client, case_id, task_assignment_id)
402
- return command.execute()
403
-
404
- def import_task_assignments(self, case_id: str, task_assignment_ids: List[str]) -> Dict[str, Any]:
405
- """Import task assignments to a case."""
406
- command = ImportTaskAssignmentsToCaseCommand(self.http_client, case_id, task_assignment_ids)
407
- return command.execute()
408
-
409
- def add_note(self, case_id: str, note_value: str) -> CaseNote:
410
- """Add a note to a case."""
411
- command = AddNoteToCaseCommand(self.http_client, case_id, note_value)
412
- return command.execute()
413
-
414
- def update_note(self, case_id: str, note_id: str, note_value: str) -> CaseNote:
415
- """Update a note in a case."""
416
- command = UpdateNoteToCaseCommand(self.http_client, case_id, note_id, note_value)
417
- return command.execute()
418
-
419
- def delete_note(self, case_id: str, note_id: str) -> Dict[str, Any]:
420
- """Delete a note from a case."""
421
- command = DeleteNoteToCaseCommand(self.http_client, case_id, note_id)
422
- return command.execute()
423
-
424
- def export_notes(self, case_id: str) -> Dict[str, Any]:
425
- """Export case notes as a file download (ZIP/CSV format)."""
426
- command = ExportCaseNotesCommand(self.http_client, case_id)
427
- return command.execute()
428
-
429
- def export_cases(self, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
430
- """Export cases as a CSV file download."""
431
- command = ExportCasesCommand(self.http_client, filter_params)
432
- return command.execute()
433
-
434
- def export_endpoints(self, case_id: str, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
435
- """Export case endpoints as a CSV file download with optional filtering."""
436
- command = ExportCaseEndpointsCommand(self.http_client, case_id, filter_params)
437
- return command.execute()
438
-
439
- def export_activities(self, case_id: str, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
440
- """Export case activities as a CSV file download with optional filtering and pagination."""
441
- command = ExportCaseActivitiesCommand(self.http_client, case_id, filter_params)
442
- return command.execute()
443
-
444
-
445
- class TasksAPI:
446
- """Tasks API with CQRS pattern - separated queries and commands."""
447
-
448
- def __init__(self, http_client: HTTPClient):
449
- self.http_client = http_client
450
-
451
- # QUERIES (Read operations)
452
- def list(self, filter_params: Optional[TaskFilter] = None, organization_ids: Optional[List[int]] = None) -> List[Task]:
453
- """List tasks with optional filtering."""
454
- query = ListTasksQuery(self.http_client, filter_params, organization_ids)
455
- return query.execute()
456
-
457
- def get(self, task_id: str) -> Task:
458
- """Get a specific task by ID."""
459
- query = GetTaskQuery(self.http_client, task_id)
460
- return query.execute()
461
-
462
- def get_assignments(self, task_id: str) -> List[TaskAssignment]:
463
- """Get task assignments for a specific task."""
464
- query = GetTaskAssignmentsQuery(self.http_client, task_id)
465
- return query.execute()
466
-
467
- # COMMANDS (Write operations)
468
- def cancel(self, task_id: str) -> Dict[str, Any]:
469
- """Cancel a task."""
470
- command = CancelTaskCommand(self.http_client, task_id)
471
- return command.execute()
472
-
473
- def cancel_assignment(self, assignment_id: str) -> Dict[str, Any]:
474
- """Cancel a task assignment."""
475
- command = CancelTaskAssignmentCommand(self.http_client, assignment_id)
476
- return command.execute()
477
-
478
- def delete_assignment(self, assignment_id: str) -> Dict[str, Any]:
479
- """Delete a task assignment."""
480
- command = DeleteTaskAssignmentCommand(self.http_client, assignment_id)
481
- return command.execute()
482
-
483
- def delete(self, task_id: str) -> Dict[str, Any]:
484
- """Delete a task."""
485
- command = DeleteTaskCommand(self.http_client, task_id)
486
- return command.execute()
487
-
488
-
489
- class AcquisitionsAPI:
490
- """Acquisitions API with CQRS pattern - separated queries and commands."""
491
-
492
- def __init__(self, http_client: HTTPClient):
493
- self.http_client = http_client
494
-
495
- # QUERIES (Read operations)
496
- def list_profiles(
497
- self,
498
- filter_params: Optional[AcquisitionFilter] = None,
499
- organization_ids: Optional[List[int]] = None,
500
- all_organizations: bool = False
501
- ) -> List[AcquisitionProfile]:
502
- """List acquisition profiles with optional filtering."""
503
- query = ListAcquisitionProfilesQuery(self.http_client, filter_params, organization_ids, all_organizations)
504
- return query.execute()
505
-
506
- def get_profile(self, profile_id: str) -> AcquisitionProfileDetails:
507
- """Get a specific acquisition profile by ID."""
508
- query = GetAcquisitionProfileQuery(self.http_client, profile_id)
509
- return query.execute()
510
-
511
- # COMMANDS (Write operations)
512
- def acquire(self, request) -> Dict[str, Any]:
513
- """Assign evidence acquisition task by filter."""
514
- command = CreateAcquisitionCommand(self.http_client, request)
515
- return command.execute()
516
-
517
- def acquire_image(self, request) -> Dict[str, Any]:
518
- """Assign image acquisition task by filter."""
519
- command = CreateImageAcquisitionCommand(self.http_client, request)
520
- return command.execute()
521
-
522
- def create_profile(self, request: CreateAcquisitionProfileRequest) -> Dict[str, Any]:
523
- """Create acquisition profile."""
524
- command = CreateAcquisitionProfileCommand(self.http_client, request)
525
- return command.execute()
526
-
527
- # Legacy method aliases for backwards compatibility
528
- def assign_task(self, request: AcquisitionTaskRequest) -> List[Dict[str, Any]]:
529
- """Legacy alias for acquire method."""
530
- command = AssignAcquisitionTaskCommand(self.http_client, request)
531
- return command.execute()
532
-
533
- def assign_image_task(self, request: ImageAcquisitionTaskRequest) -> List[Dict[str, Any]]:
534
- """Legacy alias for acquire_image method."""
535
- command = AssignImageAcquisitionTaskCommand(self.http_client, request)
536
- return command.execute()
537
-
538
-
539
- class PoliciesAPI:
540
- """Policies API with CQRS pattern - separated queries and commands."""
541
-
542
- def __init__(self, http_client: HTTPClient):
543
- self.http_client = http_client
544
-
545
- # QUERIES (Read operations)
546
- def list(self, filter_params: Optional[PolicyFilter] = None, organization_ids: Optional[List[int]] = None) -> List[Policy]:
547
- """List policies with optional filtering."""
548
- from .queries.policies import ListPoliciesQuery
549
- query = ListPoliciesQuery(self.http_client, filter_params, organization_ids)
550
- result = query.execute()
551
- # Extract the policies list from the paginated response
552
- if hasattr(result, 'entities'):
553
- return result.entities
554
- elif isinstance(result, list):
555
- return result
556
- else:
557
- return []
558
-
559
- def get(self, policy_id: str) -> Policy:
560
- """Get a specific policy by ID."""
561
- from .queries.policies import GetPolicyQuery
562
- query = GetPolicyQuery(self.http_client, policy_id)
563
- return query.execute()
564
-
565
- def get_assignments(self, policy_id: str) -> List[PolicyAssignment]:
566
- """Get policy assignments."""
567
- from .queries.policies import GetPolicyAssignmentsQuery
568
- query = GetPolicyAssignmentsQuery(self.http_client, policy_id)
569
- return query.execute()
570
-
571
- def get_executions(self, policy_id: str) -> List[PolicyExecution]:
572
- """Get policy executions."""
573
- from .queries.policies import GetPolicyExecutionsQuery
574
- query = GetPolicyExecutionsQuery(self.http_client, policy_id)
575
- return query.execute()
576
-
577
- def get_match_stats(self, filter_params: Optional[Dict[str, Any]] = None, organization_ids: Optional[List[int]] = None) -> Dict[str, Any]:
578
- """Get policy match statistics with filtering.
579
-
580
- Args:
581
- filter_params: Optional filter parameters (name, platform, tags, etc.)
582
- organization_ids: List of organization IDs (defaults to [0])
583
-
584
- Returns:
585
- Dictionary containing policy match statistics
586
- """
587
- try:
588
- # Fix API-001: Ensure organizationIds are provided to prevent errors
589
- if organization_ids is None or len(organization_ids) == 0:
590
- organization_ids = [0] # Default to organization 0
591
-
592
- # Build payload with default filter structure
593
- payload = {
594
- "name": "",
595
- "searchTerm": "",
596
- "ipAddress": "",
597
- "groupId": "",
598
- "groupFullPath": "",
599
- "managedStatus": [],
600
- "isolationStatus": [],
601
- "platform": [],
602
- "issue": "",
603
- "onlineStatus": [],
604
- "tags": [],
605
- "version": "",
606
- "policy": "",
607
- "includedEndpointIds": [],
608
- "excludedEndpointIds": [],
609
- "organizationIds": organization_ids
610
- }
611
-
612
- # Apply custom filter parameters if provided
613
- if filter_params:
614
- for key, value in filter_params.items():
615
- if key in payload:
616
- payload[key] = value
617
-
618
- # Use correct API endpoint: POST policies/match-stats (not GET policies/stats)
619
- response = self.http_client.post("policies/match-stats", json_data=payload)
620
- return response
621
-
622
- except Exception as e:
623
- # Return a simulated response for testing
624
- return {
625
- "success": False,
626
- "error": str(e),
627
- "result": []
628
- }
629
-
630
- # COMMANDS (Write operations)
631
- def create(self, policy_data: Union[CreatePolicyRequest, Dict[str, Any]]) -> Policy:
632
- """Create a new policy."""
633
- from .commands.policies import CreatePolicyCommand
634
- command = CreatePolicyCommand(self.http_client, policy_data)
635
- return command.execute()
636
-
637
- def update(self, policy_id: str, update_data: Union[UpdatePolicyRequest, Dict[str, Any]]) -> Policy:
638
- """Update an existing policy."""
639
- from .commands.policies import UpdatePolicyCommand
640
- command = UpdatePolicyCommand(self.http_client, policy_id, update_data)
641
- return command.execute()
642
-
643
- def delete(self, policy_id: str) -> Dict[str, Any]:
644
- """Delete a policy."""
645
- from .commands.policies import DeletePolicyCommand
646
- command = DeletePolicyCommand(self.http_client, policy_id)
647
- return command.execute()
648
-
649
- def activate(self, policy_id: str) -> Policy:
650
- """Activate a policy."""
651
- from .commands.policies import ActivatePolicyCommand
652
- command = ActivatePolicyCommand(self.http_client, policy_id)
653
- return command.execute()
654
-
655
- def deactivate(self, policy_id: str) -> Policy:
656
- """Deactivate a policy."""
657
- from .commands.policies import DeactivatePolicyCommand
658
- command = DeactivatePolicyCommand(self.http_client, policy_id)
659
- return command.execute()
660
-
661
- def assign(self, assignment_data: Union[AssignPolicyRequest, Dict[str, Any]]) -> Dict[str, Any]:
662
- """Assign policy to endpoints."""
663
- from .commands.policies import AssignPolicyCommand
664
- command = AssignPolicyCommand(self.http_client, assignment_data)
665
- return command.execute()
666
-
667
- def unassign(self, policy_id: str, endpoint_ids: List[str]) -> Dict[str, Any]:
668
- """Unassign policy from endpoints."""
669
- from .commands.policies import UnassignPolicyCommand
670
- command = UnassignPolicyCommand(self.http_client, policy_id, endpoint_ids)
671
- return command.execute()
672
-
673
- def execute(self, policy_id: str, endpoint_ids: Optional[List[str]] = None) -> Dict[str, Any]:
674
- """Execute a policy on assigned endpoints."""
675
- from .commands.policies import ExecutePolicyCommand
676
- command = ExecutePolicyCommand(self.http_client, policy_id, endpoint_ids)
677
- return command.execute()
678
-
679
- def update_priorities(self, policy_ids: List[str], organization_ids: Optional[List[int]] = None) -> Dict[str, Any]:
680
- """Update policy priorities.
681
-
682
- Args:
683
- policy_ids: List of policy IDs in priority order (System policy must be first)
684
- organization_ids: List of organization IDs (defaults to [0])
685
-
686
- Returns:
687
- Response dictionary with success status
688
- """
689
- try:
690
- # Fix API-001: Ensure organizationIds are provided to prevent issues
691
- if organization_ids is None or len(organization_ids) == 0:
692
- organization_ids = [0] # Default to organization 0
693
-
694
- # Use correct API parameter names according to specification
695
- payload = {
696
- "ids": policy_ids, # API expects 'ids', not 'policyIds'
697
- "organizationIds": organization_ids # Required parameter
698
- }
699
-
700
- response = self.http_client.put("policies/priorities", json_data=payload)
701
- return response
702
- except Exception as e:
703
- # Return a simulated response for testing
704
- return {
705
- "success": False,
706
- "error": str(e),
707
- "updated_policies": []
708
- }
709
-
710
-
711
- class OrganizationsAPI:
712
- """Organizations API with CQRS pattern - separated queries and commands."""
713
-
714
- def __init__(self, http_client: HTTPClient):
715
- self.http_client = http_client
716
-
717
- # QUERIES (Read operations)
718
- def list(self, page: int = 1, page_size: int = 10,
719
- sort_by: str = "name", order: str = "asc") -> OrganizationsPaginatedResponse:
720
- """List organizations with pagination and sorting."""
721
- from .queries.organizations import ListOrganizationsQuery
722
- query = ListOrganizationsQuery(self.http_client, page, page_size, sort_by, order, None)
723
- return query.execute()
724
-
725
- def get(self, organization_id: str) -> Organization:
726
- """Get organization by ID."""
727
- from .queries.organizations import GetOrganizationQuery
728
- query = GetOrganizationQuery(self.http_client, organization_id)
729
- return query.execute()
730
-
731
- def get_users(self, organization_id: str, page: int = 1, page_size: int = 10) -> OrganizationUsersPaginatedResponse:
732
- """Get users in organization."""
733
- from .queries.organizations import GetOrganizationUsersQuery
734
- query = GetOrganizationUsersQuery(self.http_client, organization_id, page, page_size)
735
- return query.execute()
736
-
737
- def check_name(self, name: str) -> bool:
738
- """Check if organization name exists."""
739
- try:
740
- params = {"name": name}
741
- response = self.http_client.get("organizations/check", params=params)
742
- return response.get("result", False)
743
- except Exception:
744
- return False
745
-
746
- def get_shareable_deployment_info(self, deployment_token: str) -> Dict[str, Any]:
747
- """Get shareable deployment information by token."""
748
- try:
749
- response = self.http_client.get(f"organizations/shareable-deployment-info/{deployment_token}")
750
-
751
- if response.get("success"):
752
- return response.get("result", {})
753
- else:
754
- # Return error information
755
- return {
756
- "error": True,
757
- "errors": response.get("errors", []),
758
- "statusCode": response.get("statusCode", 500)
759
- }
760
- except Exception as e:
761
- return {
762
- "error": True,
763
- "errors": [str(e)],
764
- "statusCode": 500
765
- }
766
-
767
- # COMMANDS (Write operations)
768
- def create(self, request: CreateOrganizationRequest) -> Organization:
769
- """Create organization."""
770
- from .commands.organizations import CreateOrganizationCommand
771
- command = CreateOrganizationCommand(self.http_client, request)
772
- return command.execute()
773
-
774
- def update(self, organization_id: str, request: UpdateOrganizationRequest) -> Organization:
775
- """Update organization."""
776
- from .commands.organizations import UpdateOrganizationCommand
777
- command = UpdateOrganizationCommand(self.http_client, organization_id, request)
778
- return command.execute()
779
-
780
- def add_user(self, organization_id: str, request: AddUserToOrganizationRequest) -> OrganizationUser:
781
- """Add user to organization."""
782
- from .commands.organizations import AddUserToOrganizationCommand
783
- command = AddUserToOrganizationCommand(self.http_client, organization_id, request)
784
- return command.execute()
785
-
786
- def assign_users(self, organization_id: str, user_ids: List[str]) -> bool:
787
- """Assign users to organization using the /assign-users endpoint."""
788
- from .commands.organizations import AssignUsersToOrganizationCommand
789
- from .models.organizations import AssignUsersToOrganizationRequest
790
-
791
- # Create the proper request object with correct field name
792
- request = AssignUsersToOrganizationRequest(userIds=user_ids)
793
- command = AssignUsersToOrganizationCommand(self.http_client, organization_id, request)
794
- return command.execute()
795
-
796
- def remove_user(self, organization_id: str, user_id: str) -> Dict[str, Any]:
797
- """Remove user from organization using the /remove-user endpoint."""
798
- from .commands.organizations import RemoveUserFromOrganizationCommand
799
- command = RemoveUserFromOrganizationCommand(self.http_client, organization_id, user_id)
800
- return command.execute()
801
-
802
- def update_settings(self, organization_id: str, settings: Dict[str, Any]) -> OrganizationSettings:
803
- """Update organization settings."""
804
- from .commands.organizations import UpdateOrganizationSettingsCommand
805
- command = UpdateOrganizationSettingsCommand(self.http_client, organization_id, settings)
806
- return command.execute()
807
-
808
- def update_shareable_deployment_settings(self, organization_id: int, status: bool) -> Dict[str, Any]:
809
- """Update organization shareable deployment settings."""
810
- try:
811
- # Prepare the payload according to API specification
812
- payload = {"status": status}
813
-
814
- # Make the API call
815
- response = self.http_client.post(f"organizations/{organization_id}/shareable-deployment", json_data=payload)
816
- return response
817
-
818
- except Exception as e:
819
- # Check if it's a 409 conflict (expected behavior when setting to same state)
820
- error_msg = str(e)
821
- if "409" in error_msg or "already" in error_msg.lower():
822
- # Return success for 409 conflicts (expected behavior)
823
- return {
824
- "success": True,
825
- "result": None,
826
- "statusCode": 409,
827
- "message": "Shareable deployment setting already in desired state"
828
- }
829
-
830
- # Return error response format matching API
831
- return {
832
- "success": False,
833
- "result": None,
834
- "statusCode": 500,
835
- "errors": [str(e)]
836
- }
837
-
838
- def update_deployment_token(self, organization_id: int, deployment_token: str) -> Dict[str, Any]:
839
- """Update organization deployment token."""
840
- try:
841
- # Prepare the payload according to API specification
842
- payload = {"deploymentToken": deployment_token}
843
-
844
- # Make the API call
845
- response = self.http_client.post(f"organizations/{organization_id}/deployment-token", json_data=payload)
846
- return response
847
-
848
- except Exception as e:
849
- # Check if it's a 409 conflict (expected behavior when setting to same token)
850
- error_msg = str(e)
851
- if "409" in error_msg or "same token" in error_msg.lower() or "cannot be updated with same" in error_msg.lower():
852
- # Return success for 409 conflicts (expected behavior)
853
- return {
854
- "success": True,
855
- "result": None,
856
- "statusCode": 409,
857
- "message": "Deployment token already set to this value"
858
- }
859
-
860
- # Return error response format matching API
861
- return {
862
- "success": False,
863
- "result": None,
864
- "statusCode": 500,
865
- "errors": [str(e)]
866
- }
867
-
868
- def delete(self, organization_id: int) -> Dict[str, Any]:
869
- """Delete organization by ID."""
870
- try:
871
- # Make the API call
872
- response = self.http_client.delete(f"organizations/{organization_id}")
873
- return response
874
-
875
- except Exception as e:
876
- # Return error response format matching API
877
- return {
878
- "success": False,
879
- "result": None,
880
- "statusCode": 500,
881
- "errors": [str(e)]
882
- }
883
-
884
- def add_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
885
- """Add tags to organization."""
886
- try:
887
- # Prepare the payload according to API specification
888
- payload = {"tags": tags}
889
-
890
- # Make the API call using PATCH method
891
- response = self.http_client.patch(f"organizations/{organization_id}/tags", json_data=payload)
892
- return response
893
-
894
- except Exception as e:
895
- # Return error response format matching API
896
- return {
897
- "success": False,
898
- "result": None,
899
- "statusCode": 500,
900
- "errors": [str(e)]
901
- }
902
-
903
- def delete_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
904
- """Delete tags from organization."""
905
- try:
906
- # Prepare the payload according to API specification
907
- payload = {"tags": tags}
908
-
909
- # Make the API call using DELETE method
910
- response = self.http_client.delete(f"organizations/{organization_id}/tags", json_data=payload)
911
- return response
912
-
913
- except Exception as e:
914
- # Return error response format matching API
915
- return {
916
- "success": False,
917
- "result": None,
918
- "statusCode": 500,
919
- "errors": [str(e)]
920
- }
921
-
922
- def remove_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
923
- """Remove tags from organization (alias for delete_tags)."""
924
- return self.delete_tags(organization_id, tags)
925
-
926
-
927
- class TriageAPI:
928
- """Triage API with CQRS pattern - separated queries and commands."""
929
-
930
- def __init__(self, http_client: HTTPClient):
931
- self.http_client = http_client
932
-
933
- # QUERIES (Read operations)
934
- def list_rules(self, filter_params: Optional[TriageFilter] = None, organization_ids: Optional[List[int]] = None) -> List[TriageRule]:
935
- """List triage rules with optional filtering."""
936
- from .queries.triage import ListTriageRulesQuery
937
- query = ListTriageRulesQuery(self.http_client, filter_params, organization_ids)
938
- return query.execute()
939
-
940
- def get_rule(self, rule_id: str) -> TriageRule:
941
- """Get a specific triage rule by ID."""
942
- from .queries.triage import GetTriageRuleQuery
943
- query = GetTriageRuleQuery(self.http_client, rule_id)
944
- return query.execute()
945
-
946
- def get_rule_by_id(self, rule_id: str) -> TriageRule:
947
- """Get a specific triage rule by ID - alias for get_rule."""
948
- return self.get_rule(rule_id)
949
-
950
- def list_tags(self, organization_id: Optional[int] = None) -> List[TriageTag]:
951
- """List triage tags."""
952
- from .queries.triage import ListTriageTagsQuery
953
- query = ListTriageTagsQuery(self.http_client, organization_id)
954
- return query.execute()
955
-
956
- def validate_rule(self, rule_content: str, engine: str = "yara") -> Dict[str, Any]:
957
- """Validate triage rule syntax."""
958
- try:
959
- # Prepare validation data
960
- validation_data = {
961
- "rule": rule_content,
962
- "engine": engine
963
- }
964
-
965
- # Call the API validation endpoint
966
- response = self.http_client.post("triages/rules/validate", json_data=validation_data)
967
- return response
968
-
969
- except Exception as e:
970
- # Return error response format matching API
971
- return {
972
- "success": False,
973
- "result": None,
974
- "statusCode": 500,
975
- "errors": [str(e)]
976
- }
977
-
978
- # COMMANDS (Write operations)
979
- def create_rule(self, request: Union[CreateTriageRuleRequest, Dict[str, Any]]) -> TriageRule:
980
- """Create a new triage rule."""
981
- from .commands.triage import CreateTriageRuleCommand
982
- command = CreateTriageRuleCommand(self.http_client, request)
983
- return command.execute()
984
-
985
- def update_rule(self, rule_id_or_data: Union[str, Dict[str, Any]], request: Optional[Union[UpdateTriageRuleRequest, Dict[str, Any]]] = None) -> TriageRule:
986
- """Update an existing triage rule."""
987
- from .commands.triage import UpdateTriageRuleCommand
988
-
989
- # Handle both signatures: update_rule(rule_id, request) and update_rule(data_dict)
990
- if isinstance(rule_id_or_data, str) and request is not None:
991
- # Traditional signature: update_rule(rule_id, request)
992
- command = UpdateTriageRuleCommand(self.http_client, rule_id_or_data, request)
993
- elif isinstance(rule_id_or_data, dict):
994
- # Dict signature: update_rule(data_dict) where data_dict contains 'id'
995
- rule_id = rule_id_or_data.get('id')
996
- if not rule_id:
997
- raise ValueError("Rule ID must be provided in data dict or as separate parameter")
998
- command = UpdateTriageRuleCommand(self.http_client, rule_id, rule_id_or_data)
999
- else:
1000
- raise ValueError("Invalid arguments for update_rule")
1001
-
1002
- return command.execute()
1003
-
1004
- def delete_rule(self, rule_id: str) -> Dict[str, Any]:
1005
- """Delete a triage rule."""
1006
- from .commands.triage import DeleteTriageRuleCommand
1007
- command = DeleteTriageRuleCommand(self.http_client, rule_id)
1008
- return command.execute()
1009
-
1010
- def create_tag(self, request: Union[CreateTriageTagRequest, Dict[str, Any]]) -> TriageTag:
1011
- """Create a new triage tag."""
1012
- from .commands.triage import CreateTriageTagCommand
1013
- command = CreateTriageTagCommand(self.http_client, request)
1014
- return command.execute()
1015
-
1016
- def delete_tag(self, tag_id: str) -> Dict[str, Any]:
1017
- """Delete a triage tag."""
1018
- from .commands.triage import DeleteTriageTagCommand
1019
- command = DeleteTriageTagCommand(self.http_client, tag_id)
1020
- return command.execute()
1021
-
1022
- def assign_task(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
1023
- """Assign a triage task to endpoints."""
1024
- try:
1025
- # Call the correct API endpoint for triage task assignment
1026
- response = self.http_client.post("triages/triage", json_data=task_data)
1027
- return response
1028
- except Exception as e:
1029
- # Import specific exception types
1030
- from .exceptions import AuthorizationError, AuthenticationError, ValidationError, AIRAPIError
1031
-
1032
- # Handle specific API errors and preserve status codes
1033
- if isinstance(e, (AuthorizationError, AuthenticationError, ValidationError, AIRAPIError)):
1034
- # Return the actual API error response if available
1035
- if hasattr(e, 'response_data') and e.response_data:
1036
- return e.response_data
1037
- else:
1038
- # Create response matching API format with actual status code
1039
- return {
1040
- "success": False,
1041
- "result": None,
1042
- "statusCode": getattr(e, 'status_code', 500),
1043
- "errors": [str(e)]
1044
- }
1045
- else:
1046
- # For unexpected errors, use 500
1047
- return {
1048
- "success": False,
1049
- "result": None,
1050
- "statusCode": 500,
1051
- "errors": [str(e)]
1052
- }
1053
-
1054
-
1055
- class AuditAPI:
1056
- """Audit logs API with enhanced filtering capabilities."""
1057
-
1058
- def __init__(self, http_client: HTTPClient):
1059
- self.http_client = http_client
1060
-
1061
- # QUERIES (Read operations)
1062
- def list_logs(self, filter_params: Optional[AuditLogsFilter] = None, organization_ids: Optional[int] = None) -> List[AuditLog]:
1063
- """List audit logs with enhanced filtering - UPDATED for new POST-based API."""
1064
- from .queries.audit import ListAuditLogsQuery
1065
- query = ListAuditLogsQuery(self.http_client, filter_params, organization_ids)
1066
- return query.execute()
1067
-
1068
- def get_log(self, log_id: str) -> AuditLog:
1069
- """Get audit log by ID."""
1070
- from .queries.audit import GetAuditLogQuery
1071
- query = GetAuditLogQuery(self.http_client, log_id)
1072
- return query.execute()
1073
-
1074
- def get_summary(self, organization_id: int, start_date: datetime, end_date: datetime) -> AuditSummary:
1075
- """Get audit summary for a date range."""
1076
- from .queries.audit import GetAuditSummaryQuery
1077
- query = GetAuditSummaryQuery(self.http_client, organization_id, start_date, end_date)
1078
- return query.execute()
1079
-
1080
- def get_user_activity(self, organization_id: int, start_date: datetime, end_date: datetime, user_id: Optional[str] = None) -> List[AuditUserActivity]:
1081
- """Get user activity audit logs."""
1082
- from .queries.audit import GetUserActivityQuery
1083
- query = GetUserActivityQuery(self.http_client, organization_id, start_date, end_date, user_id)
1084
- return query.execute()
1085
-
1086
- def get_system_events(self, organization_id: int, start_date: datetime, end_date: datetime, severity: Optional[AuditLevel] = None) -> List[AuditSystemEvent]:
1087
- """Get system events audit logs."""
1088
- from .queries.audit import GetSystemEventsQuery
1089
- query = GetSystemEventsQuery(self.http_client, organization_id, start_date, end_date, severity)
1090
- return query.execute()
1091
-
1092
- def get_retention_policy(self, organization_id: int) -> AuditRetentionPolicy:
1093
- """Get audit retention policy."""
1094
- from .queries.audit import GetAuditRetentionPolicyQuery
1095
- query = GetAuditRetentionPolicyQuery(self.http_client, organization_id)
1096
- return query.execute()
1097
-
1098
- def export_logs(self, filter_params: Optional[AuditLogsFilter] = None, format: str = "json", organization_ids: Optional[int] = None) -> Dict[str, Any]:
1099
- """Export audit logs with enhanced filtering - UPDATED for new API."""
1100
- from .queries.audit import ExportAuditLogsQuery
1101
- query = ExportAuditLogsQuery(self.http_client, filter_params, format, organization_ids)
1102
- return query.execute()
1103
-
1104
-
1105
- class BaselineAPI:
1106
- """Baseline API with CQRS pattern - separated queries and commands."""
1107
-
1108
- def __init__(self, http_client: HTTPClient):
1109
- self.http_client = http_client
1110
-
1111
- # QUERIES (Read operations)
1112
- def list(self, filter_params: Optional[BaselineFilter] = None, organization_ids: Optional[List[int]] = None) -> List[Baseline]:
1113
- """List baselines with optional filtering."""
1114
- from .queries.baseline import ListBaselinesQuery
1115
- query = ListBaselinesQuery(self.http_client, filter_params, organization_ids)
1116
- return query.execute()
1117
-
1118
- def get(self, baseline_id: str) -> Baseline:
1119
- """Get a specific baseline by ID."""
1120
- from .queries.baseline import GetBaselineQuery
1121
- query = GetBaselineQuery(self.http_client, baseline_id)
1122
- return query.execute()
1123
-
1124
- def get_comparisons(self, baseline_id: str) -> List[BaselineComparison]:
1125
- """Get baseline comparisons."""
1126
- from .queries.baseline import GetBaselineComparisonsQuery
1127
- query = GetBaselineComparisonsQuery(self.http_client, baseline_id)
1128
- return query.execute()
1129
-
1130
- def get_comparison(self, comparison_id: str) -> BaselineComparison:
1131
- """Get a specific baseline comparison by ID."""
1132
- from .queries.baseline import GetBaselineComparisonQuery
1133
- query = GetBaselineComparisonQuery(self.http_client, comparison_id)
1134
- return query.execute()
1135
-
1136
- def list_profiles(self, organization_ids: Optional[List[int]] = None) -> List[BaselineProfile]:
1137
- """List baseline profiles."""
1138
- from .queries.baseline import ListBaselineProfilesQuery
1139
- query = ListBaselineProfilesQuery(self.http_client, organization_ids)
1140
- return query.execute()
1141
-
1142
- def get_profile(self, profile_id: str) -> BaselineProfile:
1143
- """Get a specific baseline profile by ID."""
1144
- from .queries.baseline import GetBaselineProfileQuery
1145
- query = GetBaselineProfileQuery(self.http_client, profile_id)
1146
- return query.execute()
1147
-
1148
- def get_schedules(self, baseline_id: Optional[str] = None, organization_ids: Optional[List[int]] = None) -> List[BaselineSchedule]:
1149
- """Get baseline schedules."""
1150
- from .queries.baseline import GetBaselineSchedulesQuery
1151
- query = GetBaselineSchedulesQuery(self.http_client, baseline_id, organization_ids)
1152
- return query.execute()
1153
-
1154
- # COMMANDS (Write operations)
1155
- def create(self, request: CreateBaselineRequest) -> Baseline:
1156
- """Create a new baseline."""
1157
- from .commands.baseline import CreateBaselineCommand
1158
- command = CreateBaselineCommand(self.http_client, request)
1159
- return command.execute()
1160
-
1161
- def update(self, baseline_id: str, request: UpdateBaselineRequest) -> Baseline:
1162
- """Update an existing baseline."""
1163
- from .commands.baseline import UpdateBaselineCommand
1164
- command = UpdateBaselineCommand(self.http_client, baseline_id, request)
1165
- return command.execute()
1166
-
1167
- def delete(self, baseline_id: str) -> Dict[str, Any]:
1168
- """Delete a baseline."""
1169
- from .commands.baseline import DeleteBaselineCommand
1170
- command = DeleteBaselineCommand(self.http_client, baseline_id)
1171
- return command.execute()
1172
-
1173
- def compare(self, request: CompareBaselineRequest) -> BaselineComparison:
1174
- """Run a baseline comparison."""
1175
- from .commands.baseline import CompareBaselineCommand
1176
- command = CompareBaselineCommand(self.http_client, request)
1177
- return command.execute()
1178
-
1179
- def refresh(self, baseline_id: str) -> Baseline:
1180
- """Refresh/rebuild a baseline."""
1181
- from .commands.baseline import RefreshBaselineCommand
1182
- command = RefreshBaselineCommand(self.http_client, baseline_id)
1183
- return command.execute()
1184
-
1185
- def create_profile(self, request: CreateBaselineProfileRequest) -> BaselineProfile:
1186
- """Create a new baseline profile."""
1187
- from .commands.baseline import CreateBaselineProfileCommand
1188
- command = CreateBaselineProfileCommand(self.http_client, request)
1189
- return command.execute()
1190
-
1191
- def update_profile(self, profile_id: str, request: CreateBaselineProfileRequest) -> BaselineProfile:
1192
- """Update an existing baseline profile."""
1193
- from .commands.baseline import UpdateBaselineProfileCommand
1194
- command = UpdateBaselineProfileCommand(self.http_client, profile_id, request)
1195
- return command.execute()
1196
-
1197
- def delete_profile(self, profile_id: str) -> Dict[str, Any]:
1198
- """Delete a baseline profile."""
1199
- from .commands.baseline import DeleteBaselineProfileCommand
1200
- command = DeleteBaselineProfileCommand(self.http_client, profile_id)
1201
- return command.execute()
1202
-
1203
- def create_schedule(self, baseline_id: str, schedule_data: Dict[str, Any]) -> BaselineSchedule:
1204
- """Create a baseline schedule."""
1205
- from .commands.baseline import CreateBaselineScheduleCommand
1206
- command = CreateBaselineScheduleCommand(self.http_client, baseline_id, schedule_data)
1207
- return command.execute()
1208
-
1209
- def delete_schedule(self, schedule_id: str) -> Dict[str, Any]:
1210
- """Delete a baseline schedule."""
1211
- from .commands.baseline import DeleteBaselineScheduleCommand
1212
- command = DeleteBaselineScheduleCommand(self.http_client, schedule_id)
1213
- return command.execute()
1214
-
1215
- def get_comparison_report(self, baseline_id: str, task_id: str) -> BaselineComparison:
1216
- """Get comparison report - alias for get_comparison method (maintaining backward compatibility)."""
1217
- # Use the comparison_id as the primary lookup since that's what the query expects
1218
- return self.get_comparison(task_id)
1219
-
1220
- def acquire(self, baseline_data: Dict[str, Any]) -> Baseline:
1221
- """Acquire baseline - wrapper for create method (maintaining backward compatibility)."""
1222
- # Convert dict to CreateBaselineRequest if needed
1223
- from .models.baseline import CreateBaselineRequest
1224
- if isinstance(baseline_data, dict):
1225
- request = CreateBaselineRequest(**baseline_data)
1226
- else:
1227
- request = baseline_data
1228
- return self.create(request)
1229
-
1230
- def acquire_by_filter(self, filter_data: Dict[str, Any], case_id: Optional[str] = None) -> Dict[str, Any]:
1231
- """Acquire baselines by asset filter criteria."""
1232
- from .commands.baseline import AcquireBaselineByFilterCommand
1233
-
1234
- payload = {
1235
- "filter": filter_data,
1236
- "caseId": case_id
1237
- }
1238
-
1239
- command = AcquireBaselineByFilterCommand(self.http_client, payload)
1240
- return command.execute()
1241
-
1242
- def compare_by_endpoint(self, endpoint_id: str, baseline_task_ids: List[str]) -> Dict[str, Any]:
1243
- """Compare baseline acquisition tasks by endpoint ID."""
1244
- from .commands.baseline import CompareBaselineByEndpointCommand
1245
-
1246
- payload = {
1247
- "endpointId": endpoint_id,
1248
- "taskIds": baseline_task_ids
1249
- }
1250
-
1251
- command = CompareBaselineByEndpointCommand(self.http_client, payload)
1252
- return command.execute()
1253
-
1254
-
1255
- class AIRClient:
1256
- """Main client for the Binalyze AIR API using CQRS architecture."""
1257
-
1258
- def __init__(
1259
- self,
1260
- host: Optional[str] = None,
1261
- api_token: Optional[str] = None,
1262
- organization_id: Optional[int] = None,
1263
- config_file: Optional[str] = None,
1264
- config: Optional[AIRConfig] = None,
1265
- **kwargs
1266
- ):
1267
- """
1268
- Initialize the AIR client.
1269
-
1270
- Args:
1271
- host: AIR instance host URL
1272
- api_token: API token for authentication
1273
- organization_id: Default organization ID
1274
- config_file: Path to configuration file
1275
- config: Pre-configured AIRConfig instance
1276
- **kwargs: Additional configuration options
1277
- """
1278
- if config:
1279
- self.config = config
1280
- else:
1281
- self.config = AIRConfig.create(
1282
- host=host,
1283
- api_token=api_token,
1284
- organization_id=organization_id,
1285
- config_file=config_file,
1286
- **kwargs
1287
- )
1288
-
1289
- self.http_client = HTTPClient(self.config)
1290
-
1291
- # Initialize API sections using CQRS pattern
1292
- self.assets = AssetsAPI(self.http_client)
1293
- self.cases = CasesAPI(self.http_client)
1294
- self.tasks = TasksAPI(self.http_client)
1295
- self.acquisitions = AcquisitionsAPI(self.http_client)
1296
- self.policies = PoliciesAPI(self.http_client)
1297
- self.organizations = OrganizationsAPI(self.http_client)
1298
- self.triage = TriageAPI(self.http_client)
1299
- self.audit = AuditAPI(self.http_client)
1300
- self.baseline = BaselineAPI(self.http_client)
1301
-
1302
- # NEW API sections
1303
- self.authentication = AuthenticationAPI(self.http_client)
1304
- self.user_management = UsersAPI(self.http_client)
1305
- self.evidence = EvidenceAPI(self.http_client)
1306
- self.auto_asset_tags = AutoAssetTagsAPI(self.http_client)
1307
- self.evidences = EvidencesAPI(self.http_client)
1308
-
1309
- # NEWEST API sections - 5 missing categories
1310
- self.event_subscription = EventSubscriptionAPI(self.http_client)
1311
- self.interact = InteractAPI(self.http_client)
1312
- self.params = ParamsAPI(self.http_client)
1313
- self.settings = SettingsAPI(self.http_client)
1314
- self.endpoints = EndpointsAPI(self.http_client)
1315
-
1316
- # Webhook API for triggering webhook endpoints
1317
- self.webhooks = WebhookAPI(self.http_client)
1318
-
1319
- def test_connection(self) -> bool:
1320
- """Test the connection to AIR API."""
1321
- try:
1322
- # Try to check authentication as a simple test
1323
- self.authentication.check_status()
1324
- return True
1325
- except Exception:
1326
- return False
1327
-
1328
- @classmethod
1329
- def from_environment(cls) -> "AIRClient":
1330
- """Create client from environment variables."""
1331
- config = AIRConfig.from_environment()
1332
- return cls(config=config)
1333
-
1334
- @classmethod
1335
- def from_config_file(cls, config_path: str = ".air_config.json") -> "AIRClient":
1336
- """Create client from configuration file."""
1337
- config = AIRConfig.from_file(config_path)
1
+ """
2
+ Main client for the Binalyze AIR SDK using CQRS architecture.
3
+ """
4
+
5
+ import os
6
+ from typing import List, Optional, Union, Dict, Any
7
+ from datetime import datetime
8
+
9
+ from .config import AIRConfig
10
+ from .http_client import HTTPClient
11
+
12
+ # Import models
13
+ from .models.assets import Asset, AssetDetail, AssetTask, AssetFilter, AssetTaskFilter
14
+ from .models.cases import (
15
+ Case, CaseActivity, CaseEndpoint, CaseTask, User, CaseFilter, CaseActivityFilter,
16
+ CreateCaseRequest, UpdateCaseRequest, CaseNote, CaseEndpointFilter, CaseTaskFilter, CaseUserFilter
17
+ )
18
+ from .models.tasks import Task, TaskFilter, TaskAssignment
19
+ from .models.acquisitions import (
20
+ AcquisitionProfile, AcquisitionProfileDetails, AcquisitionFilter,
21
+ AcquisitionTaskRequest, ImageAcquisitionTaskRequest, CreateAcquisitionProfileRequest
22
+ )
23
+ from .models.policies import (
24
+ Policy, PolicyAssignment, PolicyExecution, PolicyFilter,
25
+ CreatePolicyRequest, UpdatePolicyRequest, AssignPolicyRequest
26
+ )
27
+ from .models.organizations import (
28
+ Organization, OrganizationUser, OrganizationRole, OrganizationLicense,
29
+ OrganizationSettings, OrganizationFilter, CreateOrganizationRequest,
30
+ UpdateOrganizationRequest, AddUserToOrganizationRequest, OrganizationsPaginatedResponse,
31
+ OrganizationUsersPaginatedResponse
32
+ )
33
+ from .models.triage import (
34
+ TriageRule, TriageTag,
35
+ TriageFilter, CreateTriageRuleRequest, UpdateTriageRuleRequest,
36
+ CreateTriageTagRequest, CreateTriageProfileRequest
37
+ )
38
+ from .models.audit import (
39
+ AuditLog, AuditSummary, AuditUserActivity, AuditSystemEvent,
40
+ AuditRetentionPolicy, AuditFilter, AuditLogsFilter, AuditLevel
41
+ )
42
+ from .models.baseline import (
43
+ Baseline, BaselineProfile, BaselineComparison, BaselineSchedule,
44
+ BaselineFilter, CreateBaselineRequest, UpdateBaselineRequest,
45
+ CreateBaselineProfileRequest, CompareBaselineRequest
46
+ )
47
+ from .models.auth import (
48
+ AuthStatus, LoginRequest, LoginResponse
49
+ )
50
+ from .models.user_management import (
51
+ UserManagementUser, CreateUserRequest, UpdateUserRequest,
52
+ AIUser, CreateAIUserRequest, APIUser, CreateAPIUserRequest, UserFilter
53
+ )
54
+ from .models.evidence import (
55
+ EvidencePPC, EvidenceReportFileInfo, EvidenceReport
56
+ )
57
+ from .models.auto_asset_tags import (
58
+ AutoAssetTag, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
59
+ StartTaggingRequest, TaggingResult, AutoAssetTagFilter
60
+ )
61
+ from .models.evidences import (
62
+ EvidenceRepository, AmazonS3Repository, AzureStorageRepository,
63
+ FTPSRepository, SFTPRepository, SMBRepository, RepositoryFilter,
64
+ CreateAmazonS3RepositoryRequest, UpdateAmazonS3RepositoryRequest,
65
+ CreateAzureStorageRepositoryRequest, UpdateAzureStorageRepositoryRequest,
66
+ CreateFTPSRepositoryRequest, UpdateFTPSRepositoryRequest,
67
+ CreateSFTPRepositoryRequest, UpdateSFTPRepositoryRequest,
68
+ CreateSMBRepositoryRequest, UpdateSMBRepositoryRequest,
69
+ ValidateRepositoryRequest, ValidationResult
70
+ )
71
+ from .models.event_subscription import (
72
+ EventSubscription, EventSubscriptionFilter, CreateEventSubscriptionRequest,
73
+ UpdateEventSubscriptionRequest
74
+ )
75
+ from .models.interact import (
76
+ ShellInteraction, AssignShellTaskRequest, ShellTaskResponse
77
+ )
78
+ from .models.params import (
79
+ AcquisitionArtifact, EDiscoveryPattern, AcquisitionEvidence, DroneAnalyzer
80
+ )
81
+ from .models.settings import (
82
+ BannerSettings, UpdateBannerSettingsRequest
83
+ )
84
+ from .models.license import (
85
+ License, LicenseUpdateRequest
86
+ )
87
+ from .models.logger import (
88
+ LogDownloadRequest, LogDownloadResponse
89
+ )
90
+ from .models.multipart_upload import (
91
+ UploadInitializeRequest, UploadInitializeResponse, UploadPartRequest, UploadPartResponse,
92
+ UploadStatusRequest, UploadStatusResponse, UploadFinalizeRequest, UploadFinalizeResponse,
93
+ MultipartUploadSession, FileChunker
94
+ )
95
+
96
+ # Import ALL API classes from their separate files
97
+ from .apis.assets import AssetsAPI
98
+ from .apis.cases import CasesAPI
99
+ from .apis.tasks import TasksAPI
100
+ from .apis.acquisitions import AcquisitionsAPI
101
+ from .apis.policies import PoliciesAPI
102
+ from .apis.organizations import OrganizationsAPI
103
+ from .apis.triage import TriageAPI
104
+ from .apis.audit_logs import AuditAPI
105
+ from .apis.baseline import BaselineAPI
106
+ from .apis.auth import AuthAPI
107
+ from .apis.evidence import EvidenceAPI
108
+ from .apis.auto_asset_tags import AutoAssetTagsAPI
109
+ from .apis.event_subscription import EventSubscriptionAPI
110
+ from .apis.interact import InteractAPI
111
+ from .apis.params import ParamsAPI
112
+ from .apis.settings import SettingsAPI
113
+ from .apis.webhooks import WebhookAPI
114
+ from .apis.api_tokens import APITokensAPI
115
+ from .apis.investigation_hub import InvestigationHubAPI
116
+ from .apis.cloud_forensics import CloudForensicsAPI
117
+ from .apis.backup import BackupAPI
118
+ from .apis.license import LicenseAPI
119
+ from .apis.logger import LoggerAPI
120
+ from .apis.multipart_upload import MultipartUploadAPI
121
+ from .apis.notifications import NotificationsAPI
122
+ from .apis.preset_filters import PresetFiltersAPI
123
+ from .apis.recent_activities import RecentActivitiesAPI
124
+ from .apis.relay_server import RelayServerAPI
125
+ from .apis.webhook_executions import WebhookExecutionsAPI
126
+ from .apis.user_management import UserManagementAPI
127
+
128
+
129
+ class AIRClient:
130
+ """Main client for the Binalyze AIR API using CQRS architecture."""
131
+
132
+ def __init__(
133
+ self,
134
+ host: Optional[str] = None,
135
+ api_token: Optional[str] = None,
136
+ organization_id: Optional[int] = None,
137
+ config_file: Optional[str] = None,
138
+ config: Optional[AIRConfig] = None,
139
+ **kwargs
140
+ ):
141
+ """
142
+ Initialize the AIR client.
143
+
144
+ Args:
145
+ host: AIR instance host URL
146
+ api_token: API token for authentication
147
+ organization_id: Default organization ID
148
+ config_file: Path to configuration file
149
+ config: Pre-configured AIRConfig instance
150
+ **kwargs: Additional configuration options
151
+ """
152
+ if config:
153
+ self.config = config
154
+ else:
155
+ self.config = AIRConfig.create(
156
+ host=host,
157
+ api_token=api_token,
158
+ organization_id=organization_id,
159
+ config_file=config_file,
160
+ **kwargs
161
+ )
162
+
163
+ self.http_client = HTTPClient(self.config)
164
+
165
+ # Initialize API sections using CQRS pattern - ALL FROM SEPARATE FILES
166
+ self.assets = AssetsAPI(self.http_client)
167
+ self.cases = CasesAPI(self.http_client)
168
+ self.tasks = TasksAPI(self.http_client)
169
+ self.acquisitions = AcquisitionsAPI(self.http_client)
170
+ self.policies = PoliciesAPI(self.http_client)
171
+ self.organizations = OrganizationsAPI(self.http_client)
172
+ self.triage = TriageAPI(self.http_client)
173
+ self.audit = AuditAPI(self.http_client)
174
+ self.baseline = BaselineAPI(self.http_client)
175
+
176
+ # Additional API sections
177
+ self.auth = AuthAPI(self.http_client)
178
+ self.user_management = UserManagementAPI(self.http_client)
179
+ self.evidence = EvidenceAPI(self.http_client)
180
+ self.auto_asset_tags = AutoAssetTagsAPI(self.http_client)
181
+ self.event_subscription = EventSubscriptionAPI(self.http_client)
182
+ self.interact = InteractAPI(self.http_client)
183
+ self.params = ParamsAPI(self.http_client)
184
+ self.settings = SettingsAPI(self.http_client)
185
+ self.webhooks = WebhookAPI(self.http_client)
186
+ self.api_tokens = APITokensAPI(self.http_client)
187
+ self.investigation_hub = InvestigationHubAPI(self.http_client)
188
+ self.cloud_forensics = CloudForensicsAPI(self.http_client)
189
+ self.backup = BackupAPI(self.http_client)
190
+ self.license = LicenseAPI(self.http_client)
191
+ self.logger = LoggerAPI(self.http_client)
192
+ self.multipart_upload = MultipartUploadAPI(self.http_client)
193
+ self.notifications = NotificationsAPI(self.http_client)
194
+ self.preset_filters = PresetFiltersAPI(self.http_client)
195
+ self.recent_activities = RecentActivitiesAPI(self.http_client)
196
+ self.relay_server = RelayServerAPI(self.http_client)
197
+ self.webhook_executions = WebhookExecutionsAPI(self.http_client)
198
+
199
+ def test_connection(self) -> bool:
200
+ """Test the connection to AIR API."""
201
+ try:
202
+ # Try to check authentication as a simple test
203
+ self.auth.check_status()
204
+ return True
205
+ except Exception:
206
+ return False
207
+
208
+ @classmethod
209
+ def from_environment(cls) -> "AIRClient":
210
+ """Create client from environment variables."""
211
+ config = AIRConfig.from_environment()
212
+ return cls(config=config)
213
+
214
+ @classmethod
215
+ def from_config_file(cls, config_path: str = ".air_config.json") -> "AIRClient":
216
+ """Create client from configuration file."""
217
+ config = AIRConfig.from_file(config_path)
1338
218
  return cls(config=config)