binalyze-air-sdk 1.0.2__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.2.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.2.dist-info/METADATA +0 -706
  141. binalyze_air_sdk-1.0.2.dist-info/RECORD +0 -82
  142. {binalyze_air_sdk-1.0.2.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,387 +1,661 @@
1
- """
2
- Acquisition-related commands for the Binalyze AIR SDK.
3
- Fixed to match API documentation exactly.
4
- """
5
-
6
- from typing import List, Dict, Any
7
-
8
- from ..base import Command
9
- from ..models.acquisitions import (
10
- AcquisitionTaskRequest, ImageAcquisitionTaskRequest, CreateAcquisitionProfileRequest,
11
- CreateAcquisitionRequest, CreateImageAcquisitionRequest
12
- )
13
- from ..models.assets import AssetFilter
14
- from ..http_client import HTTPClient
15
-
16
-
17
- class AssignAcquisitionTaskCommand(Command[List[Dict[str, Any]]]):
18
- """Command to assign acquisition task - FIXED to match API documentation exactly."""
19
-
20
- def __init__(self, http_client: HTTPClient, request: AcquisitionTaskRequest):
21
- self.http_client = http_client
22
- self.request = request
23
-
24
- def execute(self) -> List[Dict[str, Any]]:
25
- """Execute the acquisition task assignment with correct payload structure."""
26
- # FIXED: Use proper API payload structure as per documentation
27
- payload = {
28
- "caseId": self.request.case_id,
29
- "acquisitionProfileId": self.request.acquisition_profile_id,
30
- "droneConfig": {
31
- "autoPilot": self.request.drone_config.auto_pilot if self.request.drone_config else False,
32
- "enabled": self.request.drone_config.enabled if self.request.drone_config else False,
33
- "analyzers": self.request.drone_config.analyzers if self.request.drone_config else ["bha", "wsa", "aa", "ara"],
34
- "keywords": self.request.drone_config.keywords if self.request.drone_config else []
35
- },
36
- "taskConfig": {
37
- "choice": self.request.task_config.choice if self.request.task_config else "use-custom-options",
38
- "saveTo": {
39
- "windows": {
40
- "location": "local",
41
- "useMostFreeVolume": True,
42
- "repositoryId": None,
43
- "path": "Binalyze\\AIR\\",
44
- "volume": "C:",
45
- "tmp": "Binalyze\\AIR\\tmp",
46
- "directCollection": False
47
- },
48
- "linux": {
49
- "location": "local",
50
- "useMostFreeVolume": True,
51
- "repositoryId": None,
52
- "path": "opt/binalyze/air",
53
- "tmp": "opt/binalyze/air/tmp",
54
- "directCollection": False
55
- },
56
- "macos": {
57
- "location": "local",
58
- "useMostFreeVolume": False,
59
- "repositoryId": None,
60
- "path": "opt/binalyze/air",
61
- "volume": "/",
62
- "tmp": "opt/binalyze/air/tmp",
63
- "directCollection": False
64
- },
65
- "aix": {
66
- "location": "local",
67
- "useMostFreeVolume": True,
68
- "path": "opt/binalyze/air",
69
- "volume": "/",
70
- "tmp": "opt/binalyze/air/tmp",
71
- "directCollection": False
72
- }
73
- },
74
- "cpu": self.request.task_config.cpu if self.request.task_config else {"limit": 80},
75
- "compression": self.request.task_config.compression if self.request.task_config else {
76
- "enabled": True,
77
- "encryption": {
78
- "enabled": False,
79
- "password": ""
80
- }
81
- }
82
- },
83
- "filter": {
84
- "searchTerm": self.request.filter.search_term or "",
85
- "name": self.request.filter.name or "",
86
- "ipAddress": self.request.filter.ip_address or "",
87
- "groupId": self.request.filter.group_id or "",
88
- "groupFullPath": self.request.filter.group_full_path or "",
89
- "managedStatus": self.request.filter.managed_status or [],
90
- "isolationStatus": self.request.filter.isolation_status or [],
91
- "platform": self.request.filter.platform or [],
92
- "issue": self.request.filter.issue or "",
93
- "onlineStatus": self.request.filter.online_status or [],
94
- "tags": self.request.filter.tags or [],
95
- "version": self.request.filter.version or "",
96
- "policy": self.request.filter.policy or "",
97
- "includedEndpointIds": self.request.filter.included_endpoint_ids or [],
98
- "excludedEndpointIds": self.request.filter.excluded_endpoint_ids or [],
99
- "organizationIds": self.request.filter.organization_ids or [0]
100
- },
101
- "schedulerConfig": {
102
- "when": "now"
103
- }
104
- }
105
-
106
- # FIXED: Correct endpoint URL
107
- response = self.http_client.post("acquisitions/acquire", json_data=payload)
108
-
109
- return response.get("result", [])
110
-
111
-
112
- class CreateAcquisitionCommand(Command[Dict[str, Any]]):
113
- """Command to create acquisition task using simplified request - FIXED to match API."""
114
-
115
- def __init__(self, http_client: HTTPClient, request: CreateAcquisitionRequest):
116
- self.http_client = http_client
117
- self.request = request
118
-
119
- def execute(self) -> Dict[str, Any]:
120
- """Execute the acquisition task assignment with correct structure."""
121
- # FIXED: Use proper filter structure instead of direct filter object
122
- payload = {
123
- "caseId": getattr(self.request, 'case_id', None),
124
- "acquisitionProfileId": self.request.profileId,
125
- "droneConfig": {
126
- "autoPilot": False,
127
- "enabled": False,
128
- "analyzers": ["bha", "wsa", "aa", "ara"],
129
- "keywords": []
130
- },
131
- "taskConfig": {
132
- "choice": "use-custom-options",
133
- "saveTo": {
134
- "windows": {
135
- "location": "local",
136
- "useMostFreeVolume": True,
137
- "repositoryId": None,
138
- "path": "Binalyze\\AIR\\",
139
- "volume": "C:",
140
- "tmp": "Binalyze\\AIR\\tmp",
141
- "directCollection": False
142
- },
143
- "linux": {
144
- "location": "local",
145
- "useMostFreeVolume": True,
146
- "repositoryId": None,
147
- "path": "opt/binalyze/air",
148
- "tmp": "opt/binalyze/air/tmp",
149
- "directCollection": False
150
- },
151
- "macos": {
152
- "location": "local",
153
- "useMostFreeVolume": False,
154
- "repositoryId": None,
155
- "path": "opt/binalyze/air",
156
- "volume": "/",
157
- "tmp": "opt/binalyze/air/tmp",
158
- "directCollection": False
159
- },
160
- "aix": {
161
- "location": "local",
162
- "useMostFreeVolume": True,
163
- "path": "opt/binalyze/air",
164
- "volume": "/",
165
- "tmp": "opt/binalyze/air/tmp",
166
- "directCollection": False
167
- }
168
- },
169
- "cpu": {
170
- "limit": 80
171
- },
172
- "compression": {
173
- "enabled": True,
174
- "encryption": {
175
- "enabled": False,
176
- "password": ""
177
- }
178
- }
179
- },
180
- "filter": self.request.filter.to_filter_dict() if isinstance(self.request.filter, AssetFilter) else self.request.filter,
181
- "schedulerConfig": {
182
- "when": "now"
183
- }
184
- }
185
-
186
- if hasattr(self.request, 'name') and self.request.name:
187
- payload["taskName"] = self.request.name
188
-
189
- return self.http_client.post("acquisitions/acquire", json_data=payload)
190
-
191
-
192
- class AssignImageAcquisitionTaskCommand(Command[List[Dict[str, Any]]]):
193
- """Command to assign image acquisition task - FIXED endpoint URL."""
194
-
195
- def __init__(self, http_client: HTTPClient, request: ImageAcquisitionTaskRequest):
196
- self.http_client = http_client
197
- self.request = request
198
-
199
- def execute(self) -> List[Dict[str, Any]]:
200
- """Execute the image acquisition task assignment."""
201
- # Convert request to API payload using correct model attributes
202
- payload = {
203
- "caseId": self.request.case_id,
204
- "endpoints": [
205
- {
206
- "endpointId": endpoint.endpoint_id,
207
- "volumes": endpoint.volumes
208
- }
209
- for endpoint in self.request.disk_image_options.endpoints
210
- ],
211
- "organizationIds": self.request.filter.organization_ids,
212
- "startOffset": self.request.disk_image_options.start_offset,
213
- "chunkSize": self.request.disk_image_options.chunk_size,
214
- "chunkCount": self.request.disk_image_options.chunk_count,
215
- "enableCompression": self.request.task_config.compression.get("enabled", False) if self.request.task_config else False,
216
- "enableEncryption": self.request.task_config.compression.get("encryption", {}).get("enabled", False) if self.request.task_config else False,
217
- }
218
-
219
- if self.request.task_config and self.request.task_config.compression.get("encryption", {}).get("password"):
220
- payload["encryptionPassword"] = self.request.task_config.compression["encryption"]["password"]
221
-
222
- # FIXED: Correct endpoint URL
223
- response = self.http_client.post("acquisitions/acquire/image", json_data=payload)
224
-
225
- return response.get("result", [])
226
-
227
-
228
- class CreateImageAcquisitionCommand(Command[Dict[str, Any]]):
229
- """Command to create image acquisition task using simplified request - FIXED structure."""
230
-
231
- def __init__(self, http_client: HTTPClient, request: CreateImageAcquisitionRequest):
232
- self.http_client = http_client
233
- self.request = request
234
-
235
- def execute(self) -> Dict[str, Any]:
236
- """Execute the image acquisition task assignment with correct structure."""
237
- # Extract repository ID from request if available (for now use a placeholder)
238
- # This should be enhanced to get actual repository ID dynamically
239
- repository_id = getattr(self.request, 'repository_id', None)
240
-
241
- # Build the complete payload structure that matches the API specification
242
- payload = {
243
- "caseId": getattr(self.request, 'case_id', None),
244
- "taskConfig": {
245
- "choice": "use-custom-options",
246
- "saveTo": {
247
- "windows": {
248
- "location": "repository",
249
- "path": "Binalyze\\AIR\\",
250
- "useMostFreeVolume": True,
251
- "repositoryId": repository_id,
252
- "tmp": "Binalyze\\AIR\\tmp",
253
- "directCollection": False
254
- },
255
- "linux": {
256
- "location": "repository",
257
- "path": "opt/binalyze/air",
258
- "useMostFreeVolume": False,
259
- "repositoryId": repository_id,
260
- "tmp": "opt/binalyze/air/tmp",
261
- "directCollection": False
262
- },
263
- "macos": {
264
- "location": "repository",
265
- "path": "opt/binalyze/air",
266
- "useMostFreeVolume": False,
267
- "repositoryId": repository_id,
268
- "tmp": "opt/binalyze/air/tmp",
269
- "directCollection": False
270
- }
271
- },
272
- "bandwidth": {
273
- "limit": 100000
274
- },
275
- "compression": {
276
- "enabled": True,
277
- "encryption": {
278
- "enabled": False,
279
- "password": ""
280
- }
281
- }
282
- },
283
- "diskImageOptions": {
284
- "imageType": "dd",
285
- "chunkSize": 1048576,
286
- "chunkCount": 0,
287
- "startOffset": 0,
288
- "singleFile": False,
289
- "endpoints": [
290
- {
291
- "endpointId": "placeholder", # Will be replaced with actual endpoint IDs
292
- "volumes": ["/"] # Default volumes, should be replaced with actual volumes
293
- }
294
- ]
295
- },
296
- "filter": self.request.filter.to_filter_dict() if isinstance(self.request.filter, AssetFilter) else self.request.filter,
297
- "schedulerConfig": {
298
- "when": "now"
299
- }
300
- }
301
-
302
- # Extract endpoint IDs from filter and use them in diskImageOptions
303
- if isinstance(self.request.filter, dict):
304
- included_endpoints = self.request.filter.get('includedEndpointIds', [])
305
- else:
306
- included_endpoints = getattr(self.request.filter, 'included_endpoint_ids', [])
307
-
308
- # Get volumes from request or use defaults
309
- volumes = getattr(self.request, 'volumes', None) or ["/", "C:"]
310
-
311
- if included_endpoints:
312
- payload["diskImageOptions"]["endpoints"] = [
313
- {
314
- "endpointId": endpoint_id,
315
- "volumes": volumes # Use actual discovered volumes
316
- }
317
- for endpoint_id in included_endpoints
318
- ]
319
-
320
- if hasattr(self.request, 'name') and self.request.name:
321
- payload["taskName"] = self.request.name
322
-
323
- return self.http_client.post("acquisitions/acquire/image", json_data=payload)
324
-
325
-
326
- class CreateAcquisitionProfileCommand(Command[Dict[str, Any]]):
327
- """Command to create acquisition profile - FIXED endpoint URL."""
328
-
329
- def __init__(self, http_client: HTTPClient, request: CreateAcquisitionProfileRequest):
330
- self.http_client = http_client
331
- self.request = request
332
-
333
- def execute(self) -> Dict[str, Any]:
334
- """Execute the create acquisition profile command."""
335
- # Build the payload
336
- payload = {
337
- "name": self.request.name,
338
- "organizationIds": self.request.organization_ids
339
- }
340
-
341
- # Convert platform configuration to API format (snake_case -> camelCase)
342
- def convert_platform_to_api(platform_data):
343
- if not platform_data:
344
- return None
345
-
346
- api_data = {}
347
- if platform_data.get("evidence_list"):
348
- api_data["evidenceList"] = platform_data["evidence_list"]
349
- if platform_data.get("artifact_list"):
350
- api_data["artifactList"] = platform_data["artifact_list"]
351
- if platform_data.get("custom_content_profiles") is not None:
352
- api_data["customContentProfiles"] = platform_data["custom_content_profiles"]
353
-
354
- # Convert network capture configuration
355
- if platform_data.get("network_capture"):
356
- nc = platform_data["network_capture"]
357
- api_data["networkCapture"] = {
358
- "enabled": nc.get("enabled", False),
359
- "duration": nc.get("duration", 600),
360
- "pcap": nc.get("pcap", {"enabled": False}),
361
- "networkFlow": nc.get("network_flow", {"enabled": False})
362
- }
363
-
364
- return api_data
365
-
366
- # Only add platform configurations if they have content
367
- if self.request.windows:
368
- payload["windows"] = convert_platform_to_api(self.request.windows.model_dump())
369
- if self.request.linux:
370
- payload["linux"] = convert_platform_to_api(self.request.linux.model_dump())
371
- if self.request.macos:
372
- payload["macos"] = convert_platform_to_api(self.request.macos.model_dump())
373
- if self.request.aix:
374
- payload["aix"] = convert_platform_to_api(self.request.aix.model_dump())
375
-
376
- # Only add eDiscovery if it has content
377
- if self.request.e_discovery:
378
- payload["eDiscovery"] = self.request.e_discovery
379
-
380
- if self.request.description:
381
- payload["description"] = self.request.description
382
-
383
- if self.request.artifacts:
384
- payload["artifacts"] = self.request.artifacts
385
-
386
- # FIXED: Correct endpoint URL
387
- return self.http_client.post("acquisitions/profiles", json_data=payload)
1
+ """
2
+ Acquisition-related commands for the Binalyze AIR SDK.
3
+ Fixed to match API documentation exactly.
4
+ """
5
+
6
+ from typing import List, Dict, Any
7
+
8
+ from ..base import Command
9
+ from ..models.acquisitions import (
10
+ AcquisitionTaskRequest, ImageAcquisitionTaskRequest, CreateAcquisitionProfileRequest,
11
+ CreateAcquisitionRequest, CreateImageAcquisitionRequest
12
+ )
13
+ from ..models.assets import AssetFilter
14
+ from ..http_client import HTTPClient
15
+
16
+
17
+ class AssignAcquisitionTaskCommand(Command[List[Dict[str, Any]]]):
18
+ """Command to assign acquisition task - FIXED to match API documentation exactly."""
19
+
20
+ def __init__(self, http_client: HTTPClient, request: AcquisitionTaskRequest):
21
+ self.http_client = http_client
22
+ self.request = request
23
+
24
+ def execute(self) -> List[Dict[str, Any]]:
25
+ """Execute the acquisition task assignment with correct payload structure."""
26
+ # FIXED: Use proper API payload structure as per documentation
27
+ payload = {
28
+ "caseId": self.request.case_id,
29
+ "acquisitionProfileId": self.request.acquisition_profile_id,
30
+ "droneConfig": {
31
+ "autoPilot": self.request.drone_config.auto_pilot if self.request.drone_config else False,
32
+ "enabled": self.request.drone_config.enabled if self.request.drone_config else False,
33
+ "analyzers": self.request.drone_config.analyzers if self.request.drone_config else ["bha", "wsa", "aa", "ara"],
34
+ "keywords": self.request.drone_config.keywords if self.request.drone_config else []
35
+ },
36
+ "taskConfig": {
37
+ "choice": self.request.task_config.choice if self.request.task_config else "use-custom-options",
38
+ "saveTo": {
39
+ "windows": {
40
+ "location": "local",
41
+ "useMostFreeVolume": True,
42
+ "repositoryId": None,
43
+ "path": "Binalyze\\AIR\\",
44
+ "volume": "C:",
45
+ "tmp": "Binalyze\\AIR\\tmp",
46
+ "directCollection": False
47
+ },
48
+ "linux": {
49
+ "location": "local",
50
+ "useMostFreeVolume": True,
51
+ "repositoryId": None,
52
+ "path": "opt/binalyze/air",
53
+ "tmp": "opt/binalyze/air/tmp",
54
+ "directCollection": False
55
+ },
56
+ "macos": {
57
+ "location": "local",
58
+ "useMostFreeVolume": False,
59
+ "repositoryId": None,
60
+ "path": "opt/binalyze/air",
61
+ "volume": "/",
62
+ "tmp": "opt/binalyze/air/tmp",
63
+ "directCollection": False
64
+ },
65
+ "aix": {
66
+ "location": "local",
67
+ "useMostFreeVolume": True,
68
+ "path": "opt/binalyze/air",
69
+ "volume": "/",
70
+ "tmp": "opt/binalyze/air/tmp",
71
+ "directCollection": False
72
+ }
73
+ },
74
+ "cpu": self.request.task_config.cpu if self.request.task_config else {"limit": 80},
75
+ "compression": self.request.task_config.compression if self.request.task_config else {
76
+ "enabled": True,
77
+ "encryption": {
78
+ "enabled": False,
79
+ "password": ""
80
+ }
81
+ }
82
+ },
83
+ "filter": {
84
+ "searchTerm": self.request.filter.search_term or "",
85
+ "name": self.request.filter.name or "",
86
+ "ipAddress": self.request.filter.ip_address or "",
87
+ "groupId": self.request.filter.group_id or "",
88
+ "groupFullPath": self.request.filter.group_full_path or "",
89
+ "managedStatus": self.request.filter.managed_status or [],
90
+ "isolationStatus": self.request.filter.isolation_status or [],
91
+ "platform": self.request.filter.platform or [],
92
+ "issue": self.request.filter.issue or "",
93
+ "onlineStatus": self.request.filter.online_status or [],
94
+ "tags": self.request.filter.tags or [],
95
+ "version": self.request.filter.version or "",
96
+ "policy": self.request.filter.policy or "",
97
+ "includedEndpointIds": self.request.filter.included_endpoint_ids or [],
98
+ "excludedEndpointIds": self.request.filter.excluded_endpoint_ids or [],
99
+ "organizationIds": self.request.filter.organization_ids or [0]
100
+ },
101
+ "schedulerConfig": {
102
+ "when": "now"
103
+ }
104
+ }
105
+
106
+ # FIXED: Correct endpoint URL
107
+ response = self.http_client.post("acquisitions/acquire", json_data=payload)
108
+
109
+ return response.get("result", [])
110
+
111
+
112
+ class CreateAcquisitionCommand(Command[Dict[str, Any]]):
113
+ """Command to create acquisition task using simplified request - FIXED to match API."""
114
+
115
+ def __init__(self, http_client: HTTPClient, request: CreateAcquisitionRequest):
116
+ self.http_client = http_client
117
+ self.request = request
118
+
119
+ def execute(self) -> Dict[str, Any]:
120
+ """Execute the acquisition task assignment with correct structure."""
121
+ # FIXED: Use proper filter structure instead of direct filter object
122
+ payload = {
123
+ "caseId": getattr(self.request, 'case_id', None),
124
+ "acquisitionProfileId": self.request.profileId,
125
+ "droneConfig": {
126
+ "autoPilot": False,
127
+ "enabled": False,
128
+ "analyzers": ["bha", "wsa", "aa", "ara"],
129
+ "keywords": []
130
+ },
131
+ "taskConfig": {
132
+ "choice": "use-custom-options",
133
+ "saveTo": {
134
+ "windows": {
135
+ "location": "local",
136
+ "useMostFreeVolume": True,
137
+ "repositoryId": None,
138
+ "path": "Binalyze\\AIR\\",
139
+ "volume": "C:",
140
+ "tmp": "Binalyze\\AIR\\tmp",
141
+ "directCollection": False
142
+ },
143
+ "linux": {
144
+ "location": "local",
145
+ "useMostFreeVolume": True,
146
+ "repositoryId": None,
147
+ "path": "opt/binalyze/air",
148
+ "tmp": "opt/binalyze/air/tmp",
149
+ "directCollection": False
150
+ },
151
+ "macos": {
152
+ "location": "local",
153
+ "useMostFreeVolume": False,
154
+ "repositoryId": None,
155
+ "path": "opt/binalyze/air",
156
+ "volume": "/",
157
+ "tmp": "opt/binalyze/air/tmp",
158
+ "directCollection": False
159
+ },
160
+ "aix": {
161
+ "location": "local",
162
+ "useMostFreeVolume": True,
163
+ "path": "opt/binalyze/air",
164
+ "volume": "/",
165
+ "tmp": "opt/binalyze/air/tmp",
166
+ "directCollection": False
167
+ }
168
+ },
169
+ "cpu": {
170
+ "limit": 80
171
+ },
172
+ "compression": {
173
+ "enabled": True,
174
+ "encryption": {
175
+ "enabled": False,
176
+ "password": ""
177
+ }
178
+ }
179
+ },
180
+ "filter": self.request.filter.to_filter_dict() if isinstance(self.request.filter, AssetFilter) else self.request.filter,
181
+ "schedulerConfig": {
182
+ "when": "now"
183
+ }
184
+ }
185
+
186
+ if hasattr(self.request, 'name') and self.request.name:
187
+ payload["taskName"] = self.request.name
188
+
189
+ return self.http_client.post("acquisitions/acquire", json_data=payload)
190
+
191
+
192
+ class AssignImageAcquisitionTaskCommand(Command[List[Dict[str, Any]]]):
193
+ """Command to assign image acquisition task by filter."""
194
+
195
+ def __init__(self, http_client: HTTPClient, request: ImageAcquisitionTaskRequest):
196
+ self.http_client = http_client
197
+ self.request = request
198
+
199
+ def execute(self) -> List[Dict[str, Any]]:
200
+ """Execute the image acquisition task assignment."""
201
+
202
+ # Build payload with proper API field names (camelCase)
203
+ payload = {
204
+ "caseId": self.request.case_id,
205
+ "taskConfig": {
206
+ "choice": self.request.task_config.choice,
207
+ "saveTo": {},
208
+ "cpu": self.request.task_config.cpu,
209
+ "bandwidth": getattr(self.request.task_config, 'bandwidth', {"limit": 100000}),
210
+ "compression": self.request.task_config.compression
211
+ },
212
+ "diskImageOptions": {
213
+ "startOffset": self.request.disk_image_options.startOffset,
214
+ "chunkSize": self.request.disk_image_options.chunkSize,
215
+ "chunkCount": self.request.disk_image_options.chunkCount,
216
+ "imageType": getattr(self.request.disk_image_options, 'imageType', 'dd'),
217
+ "singleFile": getattr(self.request.disk_image_options, 'singleFile', False),
218
+ "endpoints": [
219
+ {
220
+ "endpointId": ep.endpointId,
221
+ "volumes": ep.volumes
222
+ }
223
+ for ep in self.request.disk_image_options.endpoints
224
+ ]
225
+ },
226
+ "filter": {
227
+ "searchTerm": getattr(self.request.filter, 'search_term', '') or '',
228
+ "name": getattr(self.request.filter, 'name', '') or '',
229
+ "ipAddress": getattr(self.request.filter, 'ip_address', '') or '',
230
+ "groupId": getattr(self.request.filter, 'group_id', '') or '',
231
+ "groupFullPath": getattr(self.request.filter, 'group_full_path', '') or '',
232
+ "managedStatus": getattr(self.request.filter, 'managed_status', []),
233
+ "isolationStatus": getattr(self.request.filter, 'isolation_status', []),
234
+ "platform": getattr(self.request.filter, 'platform', []),
235
+ "issue": getattr(self.request.filter, 'issue', '') or '',
236
+ "onlineStatus": getattr(self.request.filter, 'online_status', []),
237
+ "tags": getattr(self.request.filter, 'tags', []),
238
+ "version": getattr(self.request.filter, 'version', '') or '',
239
+ "policy": getattr(self.request.filter, 'policy', '') or '',
240
+ "includedEndpointIds": getattr(self.request.filter, 'included_endpoint_ids', []),
241
+ "excludedEndpointIds": getattr(self.request.filter, 'excluded_endpoint_ids', []),
242
+ "organizationIds": getattr(self.request.filter, 'organization_ids', [0])
243
+ }
244
+ }
245
+
246
+ # Build saveTo configuration with proper API field names
247
+ for platform, config in self.request.task_config.save_to.items():
248
+ if hasattr(config, 'location'):
249
+ platform_config = {
250
+ "location": config.location,
251
+ "useMostFreeVolume": config.use_most_free_volume, # FIXED: camelCase
252
+ "path": config.path,
253
+ "tmp": config.tmp,
254
+ "directCollection": config.direct_collection # FIXED: camelCase
255
+ }
256
+
257
+ # Add optional fields with proper names
258
+ if hasattr(config, 'repository_id') and config.repository_id:
259
+ platform_config["repositoryId"] = config.repository_id # FIXED: camelCase
260
+ if hasattr(config, 'volume') and config.volume:
261
+ platform_config["volume"] = config.volume
262
+
263
+ payload["taskConfig"]["saveTo"][platform] = platform_config
264
+ else:
265
+ # Handle dict-based config
266
+ payload["taskConfig"]["saveTo"][platform] = config
267
+
268
+ # Add scheduler config if present (matching API spec)
269
+ if hasattr(self.request, 'scheduler_config') and self.request.scheduler_config:
270
+ payload["schedulerConfig"] = {
271
+ "when": getattr(self.request.scheduler_config, 'when', 'now')
272
+ }
273
+ # Add other scheduler fields if present
274
+ for field in ['timezone_type', 'timezone', 'start_date', 'recurrence', 'repeat_every', 'repeat_on_week', 'repeat_on_month', 'end_repeat_type', 'end_date', 'limit']:
275
+ if hasattr(self.request.scheduler_config, field) and getattr(self.request.scheduler_config, field) is not None:
276
+ # Convert snake_case to camelCase for API
277
+ api_field = field.replace('_', '')
278
+ if field == 'timezone_type':
279
+ api_field = 'timezoneType'
280
+ elif field == 'start_date':
281
+ api_field = 'startDate'
282
+ elif field == 'repeat_every':
283
+ api_field = 'repeatEvery'
284
+ elif field == 'repeat_on_week':
285
+ api_field = 'repeatOnWeek'
286
+ elif field == 'repeat_on_month':
287
+ api_field = 'repeatOnMonth'
288
+ elif field == 'end_repeat_type':
289
+ api_field = 'endRepeatType'
290
+ elif field == 'end_date':
291
+ api_field = 'endDate'
292
+ payload["schedulerConfig"][api_field] = getattr(self.request.scheduler_config, field)
293
+ else:
294
+ # Default scheduler config as per API spec
295
+ payload["schedulerConfig"] = {"when": "now"}
296
+
297
+ response = self.http_client.post("acquisitions/acquire/image", json_data=payload)
298
+
299
+ # Extract result list from response
300
+ if isinstance(response, dict) and "result" in response:
301
+ return response["result"] if isinstance(response["result"], list) else [response["result"]]
302
+ return []
303
+
304
+
305
+ class CreateImageAcquisitionCommand(Command[Dict[str, Any]]):
306
+ """Command to create image acquisition task - FIXED with required fields."""
307
+
308
+ def __init__(self, http_client: HTTPClient, request: CreateImageAcquisitionRequest):
309
+ self.http_client = http_client
310
+ self.request = request
311
+
312
+ def execute(self) -> Dict[str, Any]:
313
+ """Execute the image acquisition task creation with proper API structure."""
314
+
315
+ # Build complete payload structure matching API specification
316
+ payload = {
317
+ "caseId": getattr(self.request, 'case_id', None)
318
+ }
319
+
320
+ # Use task_config from request if provided, otherwise use defaults
321
+ if hasattr(self.request, 'task_config') and self.request.task_config:
322
+ if isinstance(self.request.task_config, dict):
323
+ payload["taskConfig"] = self.request.task_config
324
+ else:
325
+ payload["taskConfig"] = self.request.task_config.model_dump()
326
+ else:
327
+ # Default task config
328
+ payload["taskConfig"] = {
329
+ "choice": "use-custom-options",
330
+ "saveTo": {
331
+ "windows": {
332
+ "location": "repository",
333
+ "path": "Binalyze\\AIR",
334
+ "useMostFreeVolume": True,
335
+ "repositoryId": "DEFAULT_REPOSITORY_ID",
336
+ "tmp": "Binalyze\\AIR\\tmp",
337
+ "directCollection": False
338
+ },
339
+ "linux": {
340
+ "location": "repository",
341
+ "path": "opt/binalyze/air",
342
+ "useMostFreeVolume": False,
343
+ "repositoryId": "DEFAULT_REPOSITORY_ID",
344
+ "tmp": "opt/binalyze/air/tmp",
345
+ "directCollection": False
346
+ },
347
+ "macos": {
348
+ "location": "repository",
349
+ "path": "opt/binalyze/air",
350
+ "useMostFreeVolume": False,
351
+ "repositoryId": "DEFAULT_REPOSITORY_ID",
352
+ "tmp": "opt/binalyze/air/tmp",
353
+ "directCollection": False
354
+ }
355
+ },
356
+ "cpu": {"limit": 50},
357
+ "bandwidth": {"limit": 100000},
358
+ "compression": {
359
+ "enabled": True,
360
+ "encryption": {
361
+ "enabled": False,
362
+ "password": ""
363
+ }
364
+ }
365
+ }
366
+
367
+ # Use disk_image_options from request if provided, otherwise use defaults
368
+ if hasattr(self.request, 'disk_image_options') and self.request.disk_image_options:
369
+ if isinstance(self.request.disk_image_options, dict):
370
+ payload["diskImageOptions"] = self.request.disk_image_options
371
+ else:
372
+ payload["diskImageOptions"] = self.request.disk_image_options.model_dump()
373
+ else:
374
+ # Default disk image options
375
+ payload["diskImageOptions"] = {
376
+ "chunkSize": 1048576,
377
+ "chunkCount": 0,
378
+ "startOffset": 0,
379
+ "imageType": "dd",
380
+ "singleFile": False,
381
+ "endpoints": [{
382
+ "endpointId": "SDK_TEST_NONEXISTENT_ENDPOINT",
383
+ "volumes": ["/dev/test"]
384
+ }]
385
+ }
386
+
387
+ # Use scheduler_config from request if provided, otherwise use default
388
+ if hasattr(self.request, 'scheduler_config') and self.request.scheduler_config:
389
+ if isinstance(self.request.scheduler_config, dict):
390
+ payload["schedulerConfig"] = self.request.scheduler_config
391
+ else:
392
+ payload["schedulerConfig"] = self.request.scheduler_config.model_dump()
393
+ else:
394
+ payload["schedulerConfig"] = {"when": "now"}
395
+
396
+ # Use the filter from request
397
+ if hasattr(self.request, 'filter') and self.request.filter:
398
+ if isinstance(self.request.filter, dict):
399
+ payload["filter"] = self.request.filter
400
+ else:
401
+ payload["filter"] = self.request.filter.model_dump()
402
+
403
+ # Add task name if provided
404
+ if hasattr(self.request, 'name') and self.request.name:
405
+ payload["taskName"] = self.request.name
406
+
407
+ return self.http_client.post("acquisitions/acquire/image", json_data=payload)
408
+
409
+
410
+ class CreateAcquisitionProfileCommand(Command[Dict[str, Any]]):
411
+ """Command to create acquisition profile - FIXED field conversion."""
412
+
413
+ def __init__(self, http_client: HTTPClient, request: CreateAcquisitionProfileRequest):
414
+ self.http_client = http_client
415
+ self.request = request
416
+
417
+ def execute(self) -> Dict[str, Any]:
418
+ """Execute the create acquisition profile command."""
419
+ # Build the payload
420
+ payload = {
421
+ "name": self.request.name,
422
+ "organizationIds": self.request.organizationIds if self.request.organizationIds else []
423
+ }
424
+
425
+ # Convert platform configuration to API format - FIXED conversion logic
426
+ def convert_platform_to_api(platform_data, platform_name=""):
427
+ if not platform_data:
428
+ return None
429
+
430
+ # FIXED: Use model_dump() for reliable field access
431
+ api_data = platform_data.model_dump()
432
+
433
+ # Remove networkCapture from AIX platform as per API spec
434
+ if platform_name.lower() == "aix" and "networkCapture" in api_data:
435
+ del api_data["networkCapture"]
436
+
437
+ return api_data
438
+
439
+ # Add platform configurations with proper platform names
440
+ if self.request.windows:
441
+ payload["windows"] = convert_platform_to_api(self.request.windows, "windows")
442
+ if self.request.linux:
443
+ payload["linux"] = convert_platform_to_api(self.request.linux, "linux")
444
+ if self.request.macos:
445
+ payload["macos"] = convert_platform_to_api(self.request.macos, "macos")
446
+ if self.request.aix:
447
+ payload["aix"] = convert_platform_to_api(self.request.aix, "aix")
448
+
449
+ # Handle eDiscovery field if present
450
+ if hasattr(self.request, 'eDiscovery') and self.request.eDiscovery:
451
+ # Convert EDiscoveryConfig to the expected dict format for API
452
+ if hasattr(self.request.eDiscovery, 'patterns'):
453
+ payload["eDiscovery"] = {
454
+ "patterns": [
455
+ pattern.model_dump()
456
+ for pattern in self.request.eDiscovery.patterns
457
+ ]
458
+ }
459
+ elif hasattr(self.request.eDiscovery, 'model_dump'):
460
+ # Handle as pydantic model
461
+ payload["eDiscovery"] = self.request.eDiscovery.model_dump()
462
+ elif isinstance(self.request.eDiscovery, dict):
463
+ # Handle as dictionary
464
+ payload["eDiscovery"] = self.request.eDiscovery
465
+
466
+ return self.http_client.post("acquisitions/profiles", json_data=payload)
467
+
468
+
469
+ class UpdateAcquisitionProfileCommand(Command[Dict[str, Any]]):
470
+ """Command to update acquisition profile by ID - FIXED field conversion."""
471
+
472
+ def __init__(self, http_client: HTTPClient, profile_id: str, request: CreateAcquisitionProfileRequest):
473
+ self.http_client = http_client
474
+ self.profile_id = profile_id
475
+ self.request = request
476
+
477
+ def execute(self) -> Dict[str, Any]:
478
+ """Execute the update acquisition profile command."""
479
+ # Build the payload (same structure as create)
480
+ payload = {
481
+ "name": self.request.name,
482
+ "organizationIds": self.request.organizationIds if self.request.organizationIds else []
483
+ }
484
+
485
+ # Convert platform configuration to API format - FIXED conversion logic
486
+ def convert_platform_to_api(platform_data, platform_name=""):
487
+ if not platform_data:
488
+ return None
489
+
490
+ # FIXED: Use model_dump() for reliable field access
491
+ api_data = platform_data.model_dump()
492
+
493
+ # Remove networkCapture from AIX platform as per API spec
494
+ if platform_name.lower() == "aix" and "networkCapture" in api_data:
495
+ del api_data["networkCapture"]
496
+
497
+ return api_data
498
+
499
+ # Add platform configurations with proper platform names
500
+ if self.request.windows:
501
+ payload["windows"] = convert_platform_to_api(self.request.windows, "windows")
502
+ if self.request.linux:
503
+ payload["linux"] = convert_platform_to_api(self.request.linux, "linux")
504
+ if self.request.macos:
505
+ payload["macos"] = convert_platform_to_api(self.request.macos, "macos")
506
+ if self.request.aix:
507
+ payload["aix"] = convert_platform_to_api(self.request.aix, "aix")
508
+
509
+ # Handle eDiscovery field if present
510
+ if hasattr(self.request, 'eDiscovery') and self.request.eDiscovery:
511
+ # Convert EDiscoveryConfig to the expected dict format for API
512
+ if hasattr(self.request.eDiscovery, 'patterns'):
513
+ payload["eDiscovery"] = {
514
+ "patterns": [
515
+ pattern.model_dump()
516
+ for pattern in self.request.eDiscovery.patterns
517
+ ]
518
+ }
519
+ elif hasattr(self.request.eDiscovery, 'model_dump'):
520
+ # Handle as pydantic model
521
+ payload["eDiscovery"] = self.request.eDiscovery.model_dump()
522
+ elif isinstance(self.request.eDiscovery, dict):
523
+ # Handle as dictionary
524
+ payload["eDiscovery"] = self.request.eDiscovery
525
+
526
+ return self.http_client.put(f"acquisitions/profiles/{self.profile_id}", json_data=payload)
527
+
528
+
529
+ class DeleteAcquisitionProfileCommand(Command[Dict[str, Any]]):
530
+ """Command to delete acquisition profile by ID."""
531
+
532
+ def __init__(self, http_client: HTTPClient, profile_id: str):
533
+ self.http_client = http_client
534
+ self.profile_id = profile_id
535
+
536
+ def execute(self) -> Dict[str, Any]:
537
+ """Execute the delete acquisition profile command."""
538
+ return self.http_client.delete(f"acquisitions/profiles/{self.profile_id}")
539
+
540
+
541
+ class CreateOffNetworkAcquisitionCommand(Command[Dict[str, Any]]):
542
+ """Command to create evidence acquisition off-network task."""
543
+
544
+ def __init__(self, http_client: HTTPClient, request: CreateAcquisitionRequest):
545
+ self.http_client = http_client
546
+ self.request = request
547
+
548
+ def execute(self) -> Dict[str, Any]:
549
+ """Execute the off-network acquisition task creation."""
550
+ # Build payload structure matching the API specification
551
+ payload = {
552
+ "caseId": getattr(self.request, 'case_id', None),
553
+ "acquisitionProfileId": self.request.profileId,
554
+ "droneConfig": {
555
+ "autoPilot": False,
556
+ "enabled": False,
557
+ "analyzers": ["bha", "wsa", "aa", "ara"],
558
+ "keywords": []
559
+ },
560
+ "eventLogRecordsConfig": {
561
+ "startDate": None,
562
+ "endDate": None,
563
+ "maxEventCount": 1000
564
+ },
565
+ "taskConfig": {
566
+ "choice": "use-custom-options",
567
+ "saveTo": {
568
+ "windows": {
569
+ "location": "local",
570
+ "useMostFreeVolume": True,
571
+ "repositoryId": None,
572
+ "path": "Binalyze\\AIR\\",
573
+ "volume": "C:",
574
+ "tmp": "Binalyze\\AIR\\tmp",
575
+ "directCollection": False
576
+ },
577
+ "linux": {
578
+ "location": "local",
579
+ "useMostFreeVolume": True,
580
+ "repositoryId": None,
581
+ "path": "opt/binalyze/air",
582
+ "tmp": "opt/binalyze/air/tmp",
583
+ "directCollection": False
584
+ },
585
+ "macos": {
586
+ "location": "local",
587
+ "useMostFreeVolume": False,
588
+ "repositoryId": None,
589
+ "path": "opt/binalyze/air",
590
+ "volume": "/",
591
+ "tmp": "opt/binalyze/air/tmp",
592
+ "directCollection": False
593
+ },
594
+ "aix": {
595
+ "location": "local",
596
+ "useMostFreeVolume": True,
597
+ "path": "opt/binalyze/air",
598
+ "volume": "/",
599
+ "tmp": "opt/binalyze/air/tmp",
600
+ "directCollection": False
601
+ }
602
+ },
603
+ "cpu": {
604
+ "limit": 80
605
+ },
606
+ "compression": {
607
+ "enabled": True,
608
+ "encryption": {
609
+ "enabled": False,
610
+ "password": ""
611
+ }
612
+ }
613
+ },
614
+ "filter": self.request.filter.to_filter_dict() if isinstance(self.request.filter, AssetFilter) else self.request.filter,
615
+ "schedulerConfig": {
616
+ "when": "now"
617
+ }
618
+ }
619
+
620
+ if hasattr(self.request, 'name') and self.request.name:
621
+ payload["taskName"] = self.request.name
622
+
623
+ return self.http_client.post("acquisitions/acquire/off-network", json_data=payload)
624
+
625
+
626
+ class UpdateScheduledEvidenceAcquisitionCommand(Command[Dict[str, Any]]):
627
+ """Command to update scheduled evidence acquisition."""
628
+
629
+ def __init__(self, http_client: HTTPClient, task_id: str, request: Dict[str, Any]):
630
+ self.http_client = http_client
631
+ self.task_id = task_id
632
+ self.request = request
633
+
634
+ def execute(self) -> Dict[str, Any]:
635
+ """Execute the update scheduled evidence acquisition command."""
636
+ return self.http_client.put(f"acquisitions/schedule/evidence-acquisition/{self.task_id}", json_data=self.request)
637
+
638
+
639
+ class UpdateScheduledImageAcquisitionCommand(Command[Dict[str, Any]]):
640
+ """Command to update scheduled image acquisition."""
641
+
642
+ def __init__(self, http_client: HTTPClient, task_id: str, request: Dict[str, Any]):
643
+ self.http_client = http_client
644
+ self.task_id = task_id
645
+ self.request = request
646
+
647
+ def execute(self) -> Dict[str, Any]:
648
+ """Execute the update scheduled image acquisition command."""
649
+ return self.http_client.put(f"acquisitions/schedule/image-acquisition/{self.task_id}", json_data=self.request)
650
+
651
+
652
+ class ValidateOsqueryCommand(Command[Dict[str, Any]]):
653
+ """Command to validate osquery."""
654
+
655
+ def __init__(self, http_client: HTTPClient, request: List[Dict[str, Any]]):
656
+ self.http_client = http_client
657
+ self.request = request
658
+
659
+ def execute(self) -> Dict[str, Any]:
660
+ """Execute the validate osquery command."""
661
+ return self.http_client.post("acquisitions/profiles/osquery/validate", json_data=self.request) # type: ignore