binalyze-air-sdk 1.0.1__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 (82) hide show
  1. binalyze_air/__init__.py +77 -0
  2. binalyze_air/apis/__init__.py +27 -0
  3. binalyze_air/apis/authentication.py +27 -0
  4. binalyze_air/apis/auto_asset_tags.py +75 -0
  5. binalyze_air/apis/endpoints.py +22 -0
  6. binalyze_air/apis/event_subscription.py +97 -0
  7. binalyze_air/apis/evidence.py +53 -0
  8. binalyze_air/apis/evidences.py +216 -0
  9. binalyze_air/apis/interact.py +36 -0
  10. binalyze_air/apis/params.py +40 -0
  11. binalyze_air/apis/settings.py +27 -0
  12. binalyze_air/apis/user_management.py +74 -0
  13. binalyze_air/apis/users.py +68 -0
  14. binalyze_air/apis/webhooks.py +231 -0
  15. binalyze_air/base.py +133 -0
  16. binalyze_air/client.py +1338 -0
  17. binalyze_air/commands/__init__.py +146 -0
  18. binalyze_air/commands/acquisitions.py +387 -0
  19. binalyze_air/commands/assets.py +363 -0
  20. binalyze_air/commands/authentication.py +37 -0
  21. binalyze_air/commands/auto_asset_tags.py +231 -0
  22. binalyze_air/commands/baseline.py +396 -0
  23. binalyze_air/commands/cases.py +603 -0
  24. binalyze_air/commands/event_subscription.py +102 -0
  25. binalyze_air/commands/evidences.py +988 -0
  26. binalyze_air/commands/interact.py +58 -0
  27. binalyze_air/commands/organizations.py +221 -0
  28. binalyze_air/commands/policies.py +203 -0
  29. binalyze_air/commands/settings.py +29 -0
  30. binalyze_air/commands/tasks.py +56 -0
  31. binalyze_air/commands/triage.py +360 -0
  32. binalyze_air/commands/user_management.py +126 -0
  33. binalyze_air/commands/users.py +101 -0
  34. binalyze_air/config.py +245 -0
  35. binalyze_air/exceptions.py +50 -0
  36. binalyze_air/http_client.py +306 -0
  37. binalyze_air/models/__init__.py +285 -0
  38. binalyze_air/models/acquisitions.py +251 -0
  39. binalyze_air/models/assets.py +439 -0
  40. binalyze_air/models/audit.py +273 -0
  41. binalyze_air/models/authentication.py +70 -0
  42. binalyze_air/models/auto_asset_tags.py +117 -0
  43. binalyze_air/models/baseline.py +232 -0
  44. binalyze_air/models/cases.py +276 -0
  45. binalyze_air/models/endpoints.py +76 -0
  46. binalyze_air/models/event_subscription.py +172 -0
  47. binalyze_air/models/evidence.py +66 -0
  48. binalyze_air/models/evidences.py +349 -0
  49. binalyze_air/models/interact.py +136 -0
  50. binalyze_air/models/organizations.py +294 -0
  51. binalyze_air/models/params.py +128 -0
  52. binalyze_air/models/policies.py +250 -0
  53. binalyze_air/models/settings.py +84 -0
  54. binalyze_air/models/tasks.py +149 -0
  55. binalyze_air/models/triage.py +143 -0
  56. binalyze_air/models/user_management.py +97 -0
  57. binalyze_air/models/users.py +82 -0
  58. binalyze_air/queries/__init__.py +134 -0
  59. binalyze_air/queries/acquisitions.py +156 -0
  60. binalyze_air/queries/assets.py +105 -0
  61. binalyze_air/queries/audit.py +417 -0
  62. binalyze_air/queries/authentication.py +56 -0
  63. binalyze_air/queries/auto_asset_tags.py +60 -0
  64. binalyze_air/queries/baseline.py +185 -0
  65. binalyze_air/queries/cases.py +293 -0
  66. binalyze_air/queries/endpoints.py +25 -0
  67. binalyze_air/queries/event_subscription.py +55 -0
  68. binalyze_air/queries/evidence.py +140 -0
  69. binalyze_air/queries/evidences.py +280 -0
  70. binalyze_air/queries/interact.py +28 -0
  71. binalyze_air/queries/organizations.py +223 -0
  72. binalyze_air/queries/params.py +115 -0
  73. binalyze_air/queries/policies.py +150 -0
  74. binalyze_air/queries/settings.py +20 -0
  75. binalyze_air/queries/tasks.py +82 -0
  76. binalyze_air/queries/triage.py +231 -0
  77. binalyze_air/queries/user_management.py +83 -0
  78. binalyze_air/queries/users.py +69 -0
  79. binalyze_air_sdk-1.0.1.dist-info/METADATA +635 -0
  80. binalyze_air_sdk-1.0.1.dist-info/RECORD +82 -0
  81. binalyze_air_sdk-1.0.1.dist-info/WHEEL +5 -0
  82. binalyze_air_sdk-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,146 @@
1
+ """
2
+ Command implementations for the Binalyze AIR SDK (CQRS pattern).
3
+ """
4
+
5
+ from .assets import (
6
+ IsolateAssetsCommand,
7
+ UnisolateAssetsCommand,
8
+ RebootAssetsCommand,
9
+ ShutdownAssetsCommand,
10
+ AddTagsToAssetsCommand,
11
+ RemoveTagsFromAssetsCommand,
12
+ UninstallAssetsCommand,
13
+ )
14
+ from .cases import (
15
+ CreateCaseCommand,
16
+ UpdateCaseCommand,
17
+ CloseCaseCommand,
18
+ OpenCaseCommand,
19
+ ArchiveCaseCommand,
20
+ ChangeCaseOwnerCommand,
21
+ RemoveEndpointsFromCaseCommand,
22
+ RemoveTaskAssignmentFromCaseCommand,
23
+ ImportTaskAssignmentsToCaseCommand,
24
+ )
25
+ from .tasks import (
26
+ CancelTaskCommand,
27
+ DeleteTaskCommand,
28
+ )
29
+ from .acquisitions import (
30
+ AssignAcquisitionTaskCommand,
31
+ AssignImageAcquisitionTaskCommand,
32
+ CreateAcquisitionProfileCommand,
33
+ )
34
+ from .policies import (
35
+ CreatePolicyCommand,
36
+ UpdatePolicyCommand,
37
+ DeletePolicyCommand,
38
+ ActivatePolicyCommand,
39
+ DeactivatePolicyCommand,
40
+ AssignPolicyCommand,
41
+ UnassignPolicyCommand,
42
+ ExecutePolicyCommand,
43
+ )
44
+ from .organizations import (
45
+ CreateOrganizationCommand,
46
+ UpdateOrganizationCommand,
47
+ AddUserToOrganizationCommand,
48
+ UpdateOrganizationSettingsCommand,
49
+ )
50
+ from .triage import (
51
+ CreateTriageRuleCommand,
52
+ UpdateTriageRuleCommand,
53
+ DeleteTriageRuleCommand,
54
+ EnableTriageRuleCommand,
55
+ DisableTriageRuleCommand,
56
+ CreateTriageTagCommand,
57
+ DeleteTriageTagCommand,
58
+ CreateTriageProfileCommand,
59
+ UpdateTriageProfileCommand,
60
+ DeleteTriageProfileCommand,
61
+ )
62
+ from .baseline import (
63
+ CreateBaselineCommand,
64
+ UpdateBaselineCommand,
65
+ DeleteBaselineCommand,
66
+ CompareBaselineCommand,
67
+ CreateBaselineProfileCommand,
68
+ UpdateBaselineProfileCommand,
69
+ DeleteBaselineProfileCommand,
70
+ CreateBaselineScheduleCommand,
71
+ DeleteBaselineScheduleCommand,
72
+ RefreshBaselineCommand,
73
+ )
74
+
75
+ # TODO: Add imports when implementing other endpoints
76
+
77
+ __all__ = [
78
+ # Asset commands
79
+ "IsolateAssetsCommand",
80
+ "UnisolateAssetsCommand",
81
+ "RebootAssetsCommand",
82
+ "ShutdownAssetsCommand",
83
+ "AddTagsToAssetsCommand",
84
+ "RemoveTagsFromAssetsCommand",
85
+ "UninstallAssetsCommand",
86
+
87
+ # Case commands
88
+ "CreateCaseCommand",
89
+ "UpdateCaseCommand",
90
+ "CloseCaseCommand",
91
+ "OpenCaseCommand",
92
+ "ArchiveCaseCommand",
93
+ "ChangeCaseOwnerCommand",
94
+ "RemoveEndpointsFromCaseCommand",
95
+ "RemoveTaskAssignmentFromCaseCommand",
96
+ "ImportTaskAssignmentsToCaseCommand",
97
+
98
+ # Task commands
99
+ "CancelTaskCommand",
100
+ "DeleteTaskCommand",
101
+
102
+ # Acquisition commands
103
+ "AssignAcquisitionTaskCommand",
104
+ "AssignImageAcquisitionTaskCommand",
105
+ "CreateAcquisitionProfileCommand",
106
+
107
+ # Policy commands
108
+ "CreatePolicyCommand",
109
+ "UpdatePolicyCommand",
110
+ "DeletePolicyCommand",
111
+ "ActivatePolicyCommand",
112
+ "DeactivatePolicyCommand",
113
+ "AssignPolicyCommand",
114
+ "UnassignPolicyCommand",
115
+ "ExecutePolicyCommand",
116
+
117
+ # Organization commands
118
+ "CreateOrganizationCommand",
119
+ "UpdateOrganizationCommand",
120
+ "AddUserToOrganizationCommand",
121
+ "UpdateOrganizationSettingsCommand",
122
+
123
+ # Triage commands
124
+ "CreateTriageRuleCommand",
125
+ "UpdateTriageRuleCommand",
126
+ "DeleteTriageRuleCommand",
127
+ "EnableTriageRuleCommand",
128
+ "DisableTriageRuleCommand",
129
+ "CreateTriageTagCommand",
130
+ "DeleteTriageTagCommand",
131
+ "CreateTriageProfileCommand",
132
+ "UpdateTriageProfileCommand",
133
+ "DeleteTriageProfileCommand",
134
+
135
+ # Baseline commands
136
+ "CreateBaselineCommand",
137
+ "UpdateBaselineCommand",
138
+ "DeleteBaselineCommand",
139
+ "CompareBaselineCommand",
140
+ "CreateBaselineProfileCommand",
141
+ "UpdateBaselineProfileCommand",
142
+ "DeleteBaselineProfileCommand",
143
+ "CreateBaselineScheduleCommand",
144
+ "DeleteBaselineScheduleCommand",
145
+ "RefreshBaselineCommand",
146
+ ]
@@ -0,0 +1,387 @@
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)