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.
- binalyze_air/__init__.py +77 -77
- binalyze_air/apis/__init__.py +67 -27
- binalyze_air/apis/acquisitions.py +107 -0
- binalyze_air/apis/api_tokens.py +49 -0
- binalyze_air/apis/assets.py +161 -0
- binalyze_air/apis/audit_logs.py +26 -0
- binalyze_air/apis/{authentication.py → auth.py} +29 -27
- binalyze_air/apis/auto_asset_tags.py +79 -75
- binalyze_air/apis/backup.py +177 -0
- binalyze_air/apis/baseline.py +46 -0
- binalyze_air/apis/cases.py +225 -0
- binalyze_air/apis/cloud_forensics.py +116 -0
- binalyze_air/apis/event_subscription.py +96 -96
- binalyze_air/apis/evidence.py +249 -53
- binalyze_air/apis/interact.py +153 -36
- binalyze_air/apis/investigation_hub.py +234 -0
- binalyze_air/apis/license.py +104 -0
- binalyze_air/apis/logger.py +83 -0
- binalyze_air/apis/multipart_upload.py +201 -0
- binalyze_air/apis/notifications.py +115 -0
- binalyze_air/apis/organizations.py +267 -0
- binalyze_air/apis/params.py +44 -39
- binalyze_air/apis/policies.py +186 -0
- binalyze_air/apis/preset_filters.py +79 -0
- binalyze_air/apis/recent_activities.py +71 -0
- binalyze_air/apis/relay_server.py +104 -0
- binalyze_air/apis/settings.py +395 -27
- binalyze_air/apis/tasks.py +80 -0
- binalyze_air/apis/triage.py +197 -0
- binalyze_air/apis/user_management.py +183 -74
- binalyze_air/apis/webhook_executions.py +50 -0
- binalyze_air/apis/webhooks.py +322 -230
- binalyze_air/base.py +207 -133
- binalyze_air/client.py +217 -1337
- binalyze_air/commands/__init__.py +175 -145
- binalyze_air/commands/acquisitions.py +661 -387
- binalyze_air/commands/api_tokens.py +55 -0
- binalyze_air/commands/assets.py +324 -362
- binalyze_air/commands/{authentication.py → auth.py} +36 -36
- binalyze_air/commands/auto_asset_tags.py +230 -230
- binalyze_air/commands/backup.py +47 -0
- binalyze_air/commands/baseline.py +32 -396
- binalyze_air/commands/cases.py +609 -602
- binalyze_air/commands/cloud_forensics.py +88 -0
- binalyze_air/commands/event_subscription.py +101 -101
- binalyze_air/commands/evidences.py +918 -988
- binalyze_air/commands/interact.py +172 -58
- binalyze_air/commands/investigation_hub.py +315 -0
- binalyze_air/commands/license.py +183 -0
- binalyze_air/commands/logger.py +126 -0
- binalyze_air/commands/multipart_upload.py +363 -0
- binalyze_air/commands/notifications.py +45 -0
- binalyze_air/commands/organizations.py +200 -221
- binalyze_air/commands/policies.py +175 -203
- binalyze_air/commands/preset_filters.py +55 -0
- binalyze_air/commands/recent_activities.py +32 -0
- binalyze_air/commands/relay_server.py +144 -0
- binalyze_air/commands/settings.py +431 -29
- binalyze_air/commands/tasks.py +95 -56
- binalyze_air/commands/triage.py +224 -360
- binalyze_air/commands/user_management.py +351 -126
- binalyze_air/commands/webhook_executions.py +77 -0
- binalyze_air/config.py +244 -244
- binalyze_air/exceptions.py +49 -49
- binalyze_air/http_client.py +426 -305
- binalyze_air/models/__init__.py +287 -285
- binalyze_air/models/acquisitions.py +365 -250
- binalyze_air/models/api_tokens.py +73 -0
- binalyze_air/models/assets.py +438 -438
- binalyze_air/models/audit.py +247 -272
- binalyze_air/models/audit_logs.py +14 -0
- binalyze_air/models/{authentication.py → auth.py} +69 -69
- binalyze_air/models/auto_asset_tags.py +227 -116
- binalyze_air/models/backup.py +138 -0
- binalyze_air/models/baseline.py +231 -231
- binalyze_air/models/cases.py +275 -275
- binalyze_air/models/cloud_forensics.py +145 -0
- binalyze_air/models/event_subscription.py +170 -171
- binalyze_air/models/evidence.py +65 -65
- binalyze_air/models/evidences.py +367 -348
- binalyze_air/models/interact.py +266 -135
- binalyze_air/models/investigation_hub.py +265 -0
- binalyze_air/models/license.py +150 -0
- binalyze_air/models/logger.py +83 -0
- binalyze_air/models/multipart_upload.py +352 -0
- binalyze_air/models/notifications.py +138 -0
- binalyze_air/models/organizations.py +293 -293
- binalyze_air/models/params.py +153 -127
- binalyze_air/models/policies.py +260 -249
- binalyze_air/models/preset_filters.py +79 -0
- binalyze_air/models/recent_activities.py +70 -0
- binalyze_air/models/relay_server.py +121 -0
- binalyze_air/models/settings.py +538 -84
- binalyze_air/models/tasks.py +215 -149
- binalyze_air/models/triage.py +141 -142
- binalyze_air/models/user_management.py +200 -97
- binalyze_air/models/webhook_executions.py +33 -0
- binalyze_air/queries/__init__.py +121 -133
- binalyze_air/queries/acquisitions.py +155 -155
- binalyze_air/queries/api_tokens.py +46 -0
- binalyze_air/queries/assets.py +186 -105
- binalyze_air/queries/audit.py +400 -416
- binalyze_air/queries/{authentication.py → auth.py} +55 -55
- binalyze_air/queries/auto_asset_tags.py +59 -59
- binalyze_air/queries/backup.py +66 -0
- binalyze_air/queries/baseline.py +21 -185
- binalyze_air/queries/cases.py +292 -292
- binalyze_air/queries/cloud_forensics.py +137 -0
- binalyze_air/queries/event_subscription.py +54 -54
- binalyze_air/queries/evidence.py +139 -139
- binalyze_air/queries/evidences.py +279 -279
- binalyze_air/queries/interact.py +140 -28
- binalyze_air/queries/investigation_hub.py +329 -0
- binalyze_air/queries/license.py +85 -0
- binalyze_air/queries/logger.py +58 -0
- binalyze_air/queries/multipart_upload.py +180 -0
- binalyze_air/queries/notifications.py +71 -0
- binalyze_air/queries/organizations.py +222 -222
- binalyze_air/queries/params.py +154 -115
- binalyze_air/queries/policies.py +149 -149
- binalyze_air/queries/preset_filters.py +60 -0
- binalyze_air/queries/recent_activities.py +44 -0
- binalyze_air/queries/relay_server.py +42 -0
- binalyze_air/queries/settings.py +533 -20
- binalyze_air/queries/tasks.py +125 -81
- binalyze_air/queries/triage.py +230 -230
- binalyze_air/queries/user_management.py +193 -83
- binalyze_air/queries/webhook_executions.py +39 -0
- binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
- binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
- {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
- binalyze_air/apis/endpoints.py +0 -22
- binalyze_air/apis/evidences.py +0 -216
- binalyze_air/apis/users.py +0 -68
- binalyze_air/commands/users.py +0 -101
- binalyze_air/models/endpoints.py +0 -76
- binalyze_air/models/users.py +0 -82
- binalyze_air/queries/endpoints.py +0 -25
- binalyze_air/queries/users.py +0 -69
- binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
- binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
- {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,37 +1,37 @@
|
|
1
|
-
"""
|
2
|
-
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import Dict, Any, Union
|
6
|
-
|
7
|
-
from ..base import Command
|
8
|
-
from ..models.
|
9
|
-
from ..http_client import HTTPClient
|
10
|
-
|
11
|
-
|
12
|
-
class LoginCommand(Command[LoginResponse]):
|
13
|
-
"""Command to login user."""
|
14
|
-
|
15
|
-
def __init__(self, http_client: HTTPClient, request: Union[LoginRequest, Dict[str, Any]]):
|
16
|
-
self.http_client = http_client
|
17
|
-
self.request = request
|
18
|
-
|
19
|
-
def execute(self) -> LoginResponse:
|
20
|
-
"""Execute the login command."""
|
21
|
-
# Handle both dict and model objects
|
22
|
-
if isinstance(self.request, dict):
|
23
|
-
payload = self.request
|
24
|
-
else:
|
25
|
-
payload = {
|
26
|
-
"username": self.request.username,
|
27
|
-
"password": self.request.password
|
28
|
-
}
|
29
|
-
|
30
|
-
response = self.http_client.post("auth/login", json_data=payload)
|
31
|
-
|
32
|
-
if response.get("success"):
|
33
|
-
result = response.get("result", {})
|
34
|
-
return LoginResponse(**result)
|
35
|
-
else:
|
36
|
-
# This will typically raise an exception via http_client error handling
|
1
|
+
"""
|
2
|
+
Auth-related commands for the Binalyze AIR SDK.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Union
|
6
|
+
|
7
|
+
from ..base import Command
|
8
|
+
from ..models.auth import LoginRequest, LoginResponse
|
9
|
+
from ..http_client import HTTPClient
|
10
|
+
|
11
|
+
|
12
|
+
class LoginCommand(Command[LoginResponse]):
|
13
|
+
"""Command to login user."""
|
14
|
+
|
15
|
+
def __init__(self, http_client: HTTPClient, request: Union[LoginRequest, Dict[str, Any]]):
|
16
|
+
self.http_client = http_client
|
17
|
+
self.request = request
|
18
|
+
|
19
|
+
def execute(self) -> LoginResponse:
|
20
|
+
"""Execute the login command."""
|
21
|
+
# Handle both dict and model objects
|
22
|
+
if isinstance(self.request, dict):
|
23
|
+
payload = self.request
|
24
|
+
else:
|
25
|
+
payload = {
|
26
|
+
"username": self.request.username,
|
27
|
+
"password": self.request.password
|
28
|
+
}
|
29
|
+
|
30
|
+
response = self.http_client.post("auth/login", json_data=payload)
|
31
|
+
|
32
|
+
if response.get("success"):
|
33
|
+
result = response.get("result", {})
|
34
|
+
return LoginResponse(**result)
|
35
|
+
else:
|
36
|
+
# This will typically raise an exception via http_client error handling
|
37
37
|
raise Exception(f"Login failed: {response.get('error', 'Unknown error')}")
|
@@ -1,231 +1,231 @@
|
|
1
|
-
"""
|
2
|
-
Auto Asset Tags-related commands for the Binalyze AIR SDK.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from typing import Dict, Any, Union
|
6
|
-
|
7
|
-
from ..base import Command
|
8
|
-
from ..models.auto_asset_tags import (
|
9
|
-
AutoAssetTag, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
|
10
|
-
StartTaggingRequest, TaggingResult, TaggingResponse
|
11
|
-
)
|
12
|
-
from ..http_client import HTTPClient
|
13
|
-
from ..exceptions import ServerError, ValidationError
|
14
|
-
from ..queries.auto_asset_tags import GetAutoAssetTagQuery
|
15
|
-
|
16
|
-
|
17
|
-
class CreateAutoAssetTagCommand(Command[AutoAssetTag]):
|
18
|
-
"""Command to create auto asset tag."""
|
19
|
-
|
20
|
-
def __init__(self, http_client: HTTPClient, request: Union[CreateAutoAssetTagRequest, Dict[str, Any]]):
|
21
|
-
self.http_client = http_client
|
22
|
-
self.request = request
|
23
|
-
|
24
|
-
def execute(self) -> AutoAssetTag:
|
25
|
-
"""Execute the create auto asset tag command."""
|
26
|
-
# Handle both dict and model objects
|
27
|
-
if isinstance(self.request, dict):
|
28
|
-
payload = self.request
|
29
|
-
else:
|
30
|
-
payload = self.request.model_dump(by_alias=True
|
31
|
-
|
32
|
-
# Validate payload format before sending to API
|
33
|
-
self._validate_payload_format(payload)
|
34
|
-
|
35
|
-
try:
|
36
|
-
response = self.http_client.post("auto-asset-tag", json_data=payload)
|
37
|
-
|
38
|
-
if response.get("success"):
|
39
|
-
tag_data = response.get("result", {})
|
40
|
-
return AutoAssetTag(**tag_data)
|
41
|
-
|
42
|
-
raise Exception(f"Failed to create auto asset tag: {response.get('error', 'Unknown error')}")
|
43
|
-
|
44
|
-
except ServerError as e:
|
45
|
-
if e.status_code == 500:
|
46
|
-
# Provide better error message for format issues
|
47
|
-
raise ValidationError(
|
48
|
-
f"Auto asset tag creation failed due to invalid format. "
|
49
|
-
f"Please ensure all conditions follow the required nested structure:\n"
|
50
|
-
f"- Each condition group must have 'operator' (and/or) and 'conditions' array\n"
|
51
|
-
f"- Individual conditions must have 'field', 'operator', and 'value'\n"
|
52
|
-
f"- Required fields: linuxConditions, windowsConditions, macosConditions\n"
|
53
|
-
f"Example format: {{\n"
|
54
|
-
f" 'linuxConditions': {{\n"
|
55
|
-
f" 'operator': 'and',\n"
|
56
|
-
f" 'conditions': [{{\n"
|
57
|
-
f" 'operator': 'or',\n"
|
58
|
-
f" 'conditions': [{{\n"
|
59
|
-
f" 'field': 'hostname',\n"
|
60
|
-
f" 'operator': 'contains',\n"
|
61
|
-
f" 'value': 'test'\n"
|
62
|
-
f" }}]\n"
|
63
|
-
f" }}]\n"
|
64
|
-
f" }}\n"
|
65
|
-
f"}}\n"
|
66
|
-
f"Original error: {str(e)}"
|
67
|
-
)
|
68
|
-
else:
|
69
|
-
# Re-raise other server errors
|
70
|
-
raise
|
71
|
-
|
72
|
-
def _validate_payload_format(self, payload: Dict[str, Any]) -> None:
|
73
|
-
"""Validate the payload format before sending to API."""
|
74
|
-
required_fields = ["tag"]
|
75
|
-
condition_fields = ["linuxConditions", "windowsConditions", "macosConditions"]
|
76
|
-
|
77
|
-
# Check required fields
|
78
|
-
for field in required_fields:
|
79
|
-
if field not in payload:
|
80
|
-
raise ValidationError(f"Missing required field: {field}")
|
81
|
-
|
82
|
-
# Validate at least one condition is provided
|
83
|
-
has_conditions = any(field in payload for field in condition_fields)
|
84
|
-
if not has_conditions:
|
85
|
-
raise ValidationError(
|
86
|
-
f"At least one condition type must be provided: {', '.join(condition_fields)}"
|
87
|
-
)
|
88
|
-
|
89
|
-
# Validate condition structure for provided conditions
|
90
|
-
for condition_type in condition_fields:
|
91
|
-
if condition_type in payload:
|
92
|
-
self._validate_condition_structure(payload[condition_type], condition_type)
|
93
|
-
|
94
|
-
def _validate_condition_structure(self, condition: Dict[str, Any], condition_type: str) -> None:
|
95
|
-
"""Validate the structure of a condition group."""
|
96
|
-
if not isinstance(condition, dict):
|
97
|
-
raise ValidationError(f"{condition_type} must be an object")
|
98
|
-
|
99
|
-
# Check for required fields in condition group
|
100
|
-
if "operator" not in condition:
|
101
|
-
raise ValidationError(f"{condition_type}.operator is required")
|
102
|
-
|
103
|
-
if condition["operator"] not in ["and", "or"]:
|
104
|
-
raise ValidationError(f"{condition_type}.operator must be 'and' or 'or'")
|
105
|
-
|
106
|
-
if "conditions" not in condition:
|
107
|
-
raise ValidationError(f"{condition_type}.conditions array is required")
|
108
|
-
|
109
|
-
if not isinstance(condition["conditions"], list):
|
110
|
-
raise ValidationError(f"{condition_type}.conditions must be an array")
|
111
|
-
|
112
|
-
# Validate each nested condition
|
113
|
-
for i, nested_condition in enumerate(condition["conditions"]):
|
114
|
-
if not isinstance(nested_condition, dict):
|
115
|
-
raise ValidationError(f"{condition_type}.conditions[{i}] must be an object")
|
116
|
-
|
117
|
-
# Check if it's a group condition or individual condition
|
118
|
-
if "conditions" in nested_condition:
|
119
|
-
# It's a nested group - validate recursively
|
120
|
-
self._validate_condition_structure(nested_condition, f"{condition_type}.conditions[{i}]")
|
121
|
-
else:
|
122
|
-
# It's an individual condition - validate fields
|
123
|
-
required_condition_fields = ["field", "operator", "value"]
|
124
|
-
for field in required_condition_fields:
|
125
|
-
if field not in nested_condition:
|
126
|
-
raise ValidationError(
|
127
|
-
f"{condition_type}.conditions[{i}].{field} is required"
|
128
|
-
)
|
129
|
-
|
130
|
-
|
131
|
-
class UpdateAutoAssetTagCommand(Command[AutoAssetTag]):
|
132
|
-
"""Command to update auto asset tag."""
|
133
|
-
|
134
|
-
def __init__(self, http_client: HTTPClient, tag_id: str, request: Union[UpdateAutoAssetTagRequest, Dict[str, Any]]):
|
135
|
-
self.http_client = http_client
|
136
|
-
self.tag_id = tag_id
|
137
|
-
self.request = request
|
138
|
-
|
139
|
-
def execute(self) -> AutoAssetTag:
|
140
|
-
"""Execute the update auto asset tag command."""
|
141
|
-
# Handle both dict and model objects
|
142
|
-
if isinstance(self.request, dict):
|
143
|
-
payload = self.request
|
144
|
-
else:
|
145
|
-
payload = self.request.model_dump(by_alias=True
|
146
|
-
|
147
|
-
try:
|
148
|
-
response = self.http_client.put(f"auto-asset-tag/{self.tag_id}", json_data=payload)
|
149
|
-
|
150
|
-
if response.get("success"):
|
151
|
-
tag_data = response.get("result", {})
|
152
|
-
return AutoAssetTag(**tag_data)
|
153
|
-
|
154
|
-
raise Exception(f"Failed to update auto asset tag: {response.get('error', 'Unknown error')}")
|
155
|
-
|
156
|
-
except ServerError as e:
|
157
|
-
# API-002 Workaround: If update fails with server error, provide helpful message
|
158
|
-
# with alternative approach (delete + recreate)
|
159
|
-
if "Auto asset tag update is currently unavailable" in str(e):
|
160
|
-
# Get the current tag data for reference
|
161
|
-
try:
|
162
|
-
get_query = GetAutoAssetTagQuery(self.http_client, self.tag_id)
|
163
|
-
current_tag = get_query.execute()
|
164
|
-
|
165
|
-
# Merge current data with updates
|
166
|
-
updated_data = current_tag.model_dump()
|
167
|
-
updated_data.update(payload)
|
168
|
-
|
169
|
-
raise ValidationError(
|
170
|
-
f"Auto asset tag update is currently unavailable due to a server bug. "
|
171
|
-
f"WORKAROUND: To update this tag, please:\n"
|
172
|
-
f"1. Delete the existing tag (ID: {self.tag_id})\n"
|
173
|
-
f"2. Create a new tag with the updated data:\n"
|
174
|
-
f" Tag: {updated_data.get('tag', current_tag.tag)}\n"
|
175
|
-
f" Organization IDs: {updated_data.get('organizationIds', current_tag.
|
176
|
-
f" Linux Conditions: {updated_data.get('linuxConditions', current_tag.
|
177
|
-
f" Windows Conditions: {updated_data.get('windowsConditions', current_tag.
|
178
|
-
f" macOS Conditions: {updated_data.get('macosConditions', current_tag.
|
179
|
-
f"\nUse client.auto_asset_tags.delete('{self.tag_id}') then client.auto_asset_tags.create(new_data)"
|
180
|
-
)
|
181
|
-
except Exception:
|
182
|
-
# If we can't get current data, provide generic workaround message
|
183
|
-
raise ValidationError(
|
184
|
-
f"Auto asset tag update is currently unavailable due to a server bug. "
|
185
|
-
f"WORKAROUND: Delete the existing tag (ID: {self.tag_id}) and create a new one with updated values."
|
186
|
-
)
|
187
|
-
else:
|
188
|
-
# Re-raise if it's a different server error
|
189
|
-
raise
|
190
|
-
|
191
|
-
|
192
|
-
class DeleteAutoAssetTagCommand(Command[Dict[str, Any]]):
|
193
|
-
"""Command to delete auto asset tag."""
|
194
|
-
|
195
|
-
def __init__(self, http_client: HTTPClient, tag_id: str):
|
196
|
-
self.http_client = http_client
|
197
|
-
self.tag_id = tag_id
|
198
|
-
|
199
|
-
def execute(self) -> Dict[str, Any]:
|
200
|
-
"""Execute the delete auto asset tag command."""
|
201
|
-
response = self.http_client.delete(f"auto-asset-tag/{self.tag_id}")
|
202
|
-
|
203
|
-
if response.get("success"):
|
204
|
-
return response
|
205
|
-
|
206
|
-
raise Exception(f"Failed to delete auto asset tag: {response.get('error', 'Unknown error')}")
|
207
|
-
|
208
|
-
|
209
|
-
class StartTaggingCommand(Command[TaggingResponse]):
|
210
|
-
"""Command to start tagging process."""
|
211
|
-
|
212
|
-
def __init__(self, http_client: HTTPClient, request: Union[StartTaggingRequest, Dict[str, Any]]):
|
213
|
-
self.http_client = http_client
|
214
|
-
self.request = request
|
215
|
-
|
216
|
-
def execute(self) -> TaggingResponse:
|
217
|
-
"""Execute the start tagging command."""
|
218
|
-
# Handle both dict and model objects
|
219
|
-
if isinstance(self.request, dict):
|
220
|
-
payload = self.request
|
221
|
-
else:
|
222
|
-
payload = self.request.model_dump(by_alias=True, exclude_none=True)
|
223
|
-
|
224
|
-
response = self.http_client.post("auto-asset-tag/start-tagging", json_data=payload)
|
225
|
-
|
226
|
-
if response.get("success"):
|
227
|
-
result_data = response.get("result", [])
|
228
|
-
# Handle the list response from the API
|
229
|
-
return TaggingResponse.from_api_result(result_data)
|
230
|
-
|
1
|
+
"""
|
2
|
+
Auto Asset Tags-related commands for the Binalyze AIR SDK.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Union
|
6
|
+
|
7
|
+
from ..base import Command
|
8
|
+
from ..models.auto_asset_tags import (
|
9
|
+
AutoAssetTag, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
|
10
|
+
StartTaggingRequest, TaggingResult, TaggingResponse
|
11
|
+
)
|
12
|
+
from ..http_client import HTTPClient
|
13
|
+
from ..exceptions import ServerError, ValidationError
|
14
|
+
from ..queries.auto_asset_tags import GetAutoAssetTagQuery
|
15
|
+
|
16
|
+
|
17
|
+
class CreateAutoAssetTagCommand(Command[AutoAssetTag]):
|
18
|
+
"""Command to create auto asset tag."""
|
19
|
+
|
20
|
+
def __init__(self, http_client: HTTPClient, request: Union[CreateAutoAssetTagRequest, Dict[str, Any]]):
|
21
|
+
self.http_client = http_client
|
22
|
+
self.request = request
|
23
|
+
|
24
|
+
def execute(self) -> AutoAssetTag:
|
25
|
+
"""Execute the create auto asset tag command."""
|
26
|
+
# Handle both dict and model objects
|
27
|
+
if isinstance(self.request, dict):
|
28
|
+
payload = self.request
|
29
|
+
else:
|
30
|
+
payload = self.request.model_dump(by_alias=True)
|
31
|
+
|
32
|
+
# Validate payload format before sending to API
|
33
|
+
self._validate_payload_format(payload)
|
34
|
+
|
35
|
+
try:
|
36
|
+
response = self.http_client.post("auto-asset-tag", json_data=payload)
|
37
|
+
|
38
|
+
if response.get("success"):
|
39
|
+
tag_data = response.get("result", {})
|
40
|
+
return AutoAssetTag(**tag_data)
|
41
|
+
|
42
|
+
raise Exception(f"Failed to create auto asset tag: {response.get('error', 'Unknown error')}")
|
43
|
+
|
44
|
+
except ServerError as e:
|
45
|
+
if e.status_code == 500:
|
46
|
+
# Provide better error message for format issues
|
47
|
+
raise ValidationError(
|
48
|
+
f"Auto asset tag creation failed due to invalid format. "
|
49
|
+
f"Please ensure all conditions follow the required nested structure:\n"
|
50
|
+
f"- Each condition group must have 'operator' (and/or) and 'conditions' array\n"
|
51
|
+
f"- Individual conditions must have 'field', 'operator', and 'value'\n"
|
52
|
+
f"- Required fields: linuxConditions, windowsConditions, macosConditions\n"
|
53
|
+
f"Example format: {{\n"
|
54
|
+
f" 'linuxConditions': {{\n"
|
55
|
+
f" 'operator': 'and',\n"
|
56
|
+
f" 'conditions': [{{\n"
|
57
|
+
f" 'operator': 'or',\n"
|
58
|
+
f" 'conditions': [{{\n"
|
59
|
+
f" 'field': 'hostname',\n"
|
60
|
+
f" 'operator': 'contains',\n"
|
61
|
+
f" 'value': 'test'\n"
|
62
|
+
f" }}]\n"
|
63
|
+
f" }}]\n"
|
64
|
+
f" }}\n"
|
65
|
+
f"}}\n"
|
66
|
+
f"Original error: {str(e)}"
|
67
|
+
)
|
68
|
+
else:
|
69
|
+
# Re-raise other server errors
|
70
|
+
raise
|
71
|
+
|
72
|
+
def _validate_payload_format(self, payload: Dict[str, Any]) -> None:
|
73
|
+
"""Validate the payload format before sending to API."""
|
74
|
+
required_fields = ["tag"]
|
75
|
+
condition_fields = ["linuxConditions", "windowsConditions", "macosConditions"]
|
76
|
+
|
77
|
+
# Check required fields
|
78
|
+
for field in required_fields:
|
79
|
+
if field not in payload:
|
80
|
+
raise ValidationError(f"Missing required field: {field}")
|
81
|
+
|
82
|
+
# Validate at least one condition is provided
|
83
|
+
has_conditions = any(field in payload for field in condition_fields)
|
84
|
+
if not has_conditions:
|
85
|
+
raise ValidationError(
|
86
|
+
f"At least one condition type must be provided: {', '.join(condition_fields)}"
|
87
|
+
)
|
88
|
+
|
89
|
+
# Validate condition structure for provided conditions
|
90
|
+
for condition_type in condition_fields:
|
91
|
+
if condition_type in payload:
|
92
|
+
self._validate_condition_structure(payload[condition_type], condition_type)
|
93
|
+
|
94
|
+
def _validate_condition_structure(self, condition: Dict[str, Any], condition_type: str) -> None:
|
95
|
+
"""Validate the structure of a condition group."""
|
96
|
+
if not isinstance(condition, dict):
|
97
|
+
raise ValidationError(f"{condition_type} must be an object")
|
98
|
+
|
99
|
+
# Check for required fields in condition group
|
100
|
+
if "operator" not in condition:
|
101
|
+
raise ValidationError(f"{condition_type}.operator is required")
|
102
|
+
|
103
|
+
if condition["operator"] not in ["and", "or"]:
|
104
|
+
raise ValidationError(f"{condition_type}.operator must be 'and' or 'or'")
|
105
|
+
|
106
|
+
if "conditions" not in condition:
|
107
|
+
raise ValidationError(f"{condition_type}.conditions array is required")
|
108
|
+
|
109
|
+
if not isinstance(condition["conditions"], list):
|
110
|
+
raise ValidationError(f"{condition_type}.conditions must be an array")
|
111
|
+
|
112
|
+
# Validate each nested condition
|
113
|
+
for i, nested_condition in enumerate(condition["conditions"]):
|
114
|
+
if not isinstance(nested_condition, dict):
|
115
|
+
raise ValidationError(f"{condition_type}.conditions[{i}] must be an object")
|
116
|
+
|
117
|
+
# Check if it's a group condition or individual condition
|
118
|
+
if "conditions" in nested_condition:
|
119
|
+
# It's a nested group - validate recursively
|
120
|
+
self._validate_condition_structure(nested_condition, f"{condition_type}.conditions[{i}]")
|
121
|
+
else:
|
122
|
+
# It's an individual condition - validate fields
|
123
|
+
required_condition_fields = ["field", "operator", "value"]
|
124
|
+
for field in required_condition_fields:
|
125
|
+
if field not in nested_condition:
|
126
|
+
raise ValidationError(
|
127
|
+
f"{condition_type}.conditions[{i}].{field} is required"
|
128
|
+
)
|
129
|
+
|
130
|
+
|
131
|
+
class UpdateAutoAssetTagCommand(Command[AutoAssetTag]):
|
132
|
+
"""Command to update auto asset tag."""
|
133
|
+
|
134
|
+
def __init__(self, http_client: HTTPClient, tag_id: str, request: Union[UpdateAutoAssetTagRequest, Dict[str, Any]]):
|
135
|
+
self.http_client = http_client
|
136
|
+
self.tag_id = tag_id
|
137
|
+
self.request = request
|
138
|
+
|
139
|
+
def execute(self) -> AutoAssetTag:
|
140
|
+
"""Execute the update auto asset tag command."""
|
141
|
+
# Handle both dict and model objects
|
142
|
+
if isinstance(self.request, dict):
|
143
|
+
payload = self.request
|
144
|
+
else:
|
145
|
+
payload = self.request.model_dump(by_alias=True)
|
146
|
+
|
147
|
+
try:
|
148
|
+
response = self.http_client.put(f"auto-asset-tag/{self.tag_id}", json_data=payload)
|
149
|
+
|
150
|
+
if response.get("success"):
|
151
|
+
tag_data = response.get("result", {})
|
152
|
+
return AutoAssetTag(**tag_data)
|
153
|
+
|
154
|
+
raise Exception(f"Failed to update auto asset tag: {response.get('error', 'Unknown error')}")
|
155
|
+
|
156
|
+
except ServerError as e:
|
157
|
+
# API-002 Workaround: If update fails with server error, provide helpful message
|
158
|
+
# with alternative approach (delete + recreate)
|
159
|
+
if "Auto asset tag update is currently unavailable" in str(e):
|
160
|
+
# Get the current tag data for reference
|
161
|
+
try:
|
162
|
+
get_query = GetAutoAssetTagQuery(self.http_client, self.tag_id)
|
163
|
+
current_tag = get_query.execute()
|
164
|
+
|
165
|
+
# Merge current data with updates
|
166
|
+
updated_data = current_tag.model_dump()
|
167
|
+
updated_data.update(payload)
|
168
|
+
|
169
|
+
raise ValidationError(
|
170
|
+
f"Auto asset tag update is currently unavailable due to a server bug. "
|
171
|
+
f"WORKAROUND: To update this tag, please:\n"
|
172
|
+
f"1. Delete the existing tag (ID: {self.tag_id})\n"
|
173
|
+
f"2. Create a new tag with the updated data:\n"
|
174
|
+
f" Tag: {updated_data.get('tag', current_tag.tag)}\n"
|
175
|
+
f" Organization IDs: {updated_data.get('organizationIds', current_tag.organizationIds)}\n"
|
176
|
+
f" Linux Conditions: {updated_data.get('linuxConditions', current_tag.linuxConditions)}\n"
|
177
|
+
f" Windows Conditions: {updated_data.get('windowsConditions', current_tag.windowsConditions)}\n"
|
178
|
+
f" macOS Conditions: {updated_data.get('macosConditions', current_tag.macosConditions)}\n"
|
179
|
+
f"\nUse client.auto_asset_tags.delete('{self.tag_id}') then client.auto_asset_tags.create(new_data)"
|
180
|
+
)
|
181
|
+
except Exception:
|
182
|
+
# If we can't get current data, provide generic workaround message
|
183
|
+
raise ValidationError(
|
184
|
+
f"Auto asset tag update is currently unavailable due to a server bug. "
|
185
|
+
f"WORKAROUND: Delete the existing tag (ID: {self.tag_id}) and create a new one with updated values."
|
186
|
+
)
|
187
|
+
else:
|
188
|
+
# Re-raise if it's a different server error
|
189
|
+
raise
|
190
|
+
|
191
|
+
|
192
|
+
class DeleteAutoAssetTagCommand(Command[Dict[str, Any]]):
|
193
|
+
"""Command to delete auto asset tag."""
|
194
|
+
|
195
|
+
def __init__(self, http_client: HTTPClient, tag_id: str):
|
196
|
+
self.http_client = http_client
|
197
|
+
self.tag_id = tag_id
|
198
|
+
|
199
|
+
def execute(self) -> Dict[str, Any]:
|
200
|
+
"""Execute the delete auto asset tag command."""
|
201
|
+
response = self.http_client.delete(f"auto-asset-tag/{self.tag_id}")
|
202
|
+
|
203
|
+
if response.get("success"):
|
204
|
+
return response
|
205
|
+
|
206
|
+
raise Exception(f"Failed to delete auto asset tag: {response.get('error', 'Unknown error')}")
|
207
|
+
|
208
|
+
|
209
|
+
class StartTaggingCommand(Command[TaggingResponse]):
|
210
|
+
"""Command to start tagging process."""
|
211
|
+
|
212
|
+
def __init__(self, http_client: HTTPClient, request: Union[StartTaggingRequest, Dict[str, Any]]):
|
213
|
+
self.http_client = http_client
|
214
|
+
self.request = request
|
215
|
+
|
216
|
+
def execute(self) -> TaggingResponse:
|
217
|
+
"""Execute the start tagging command."""
|
218
|
+
# Handle both dict and model objects
|
219
|
+
if isinstance(self.request, dict):
|
220
|
+
payload = self.request
|
221
|
+
else:
|
222
|
+
payload = self.request.model_dump(by_alias=True, exclude_none=True)
|
223
|
+
|
224
|
+
response = self.http_client.post("auto-asset-tag/start-tagging", json_data=payload)
|
225
|
+
|
226
|
+
if response.get("success"):
|
227
|
+
result_data = response.get("result", [])
|
228
|
+
# Handle the list response from the API
|
229
|
+
return TaggingResponse.from_api_result(result_data)
|
230
|
+
|
231
231
|
raise Exception(f"Failed to start tagging process: {response.get('error', 'Unknown error')}")
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""
|
2
|
+
Backup commands for the Binalyze AIR SDK.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any
|
6
|
+
|
7
|
+
from ..base import Command
|
8
|
+
from ..models.backup import BackupNowRequest
|
9
|
+
from ..http_client import HTTPClient
|
10
|
+
|
11
|
+
|
12
|
+
class BackupNowCommand(Command[Dict[str, Any]]):
|
13
|
+
"""Command to create an immediate backup."""
|
14
|
+
|
15
|
+
def __init__(self, http_client: HTTPClient, request: BackupNowRequest):
|
16
|
+
self.http_client = http_client
|
17
|
+
self.request = request
|
18
|
+
|
19
|
+
def execute(self) -> Dict[str, Any]:
|
20
|
+
"""Execute the command."""
|
21
|
+
# Create the filter structure based on the API specification
|
22
|
+
filter_data = {}
|
23
|
+
|
24
|
+
if self.request.include_endpoint_ids:
|
25
|
+
filter_data["includedEndpointIds"] = self.request.include_endpoint_ids
|
26
|
+
if self.request.exclude_endpoint_ids:
|
27
|
+
filter_data["excludedEndpointIds"] = self.request.exclude_endpoint_ids
|
28
|
+
if self.request.organization_ids:
|
29
|
+
filter_data["organizationIds"] = self.request.organization_ids
|
30
|
+
|
31
|
+
payload = {"filter": filter_data}
|
32
|
+
|
33
|
+
response = self.http_client.post("backup/now", json_data=payload)
|
34
|
+
return response
|
35
|
+
|
36
|
+
|
37
|
+
class DeleteBackupCommand(Command[Dict[str, Any]]):
|
38
|
+
"""Command to delete a backup."""
|
39
|
+
|
40
|
+
def __init__(self, http_client: HTTPClient, backup_id: str):
|
41
|
+
self.http_client = http_client
|
42
|
+
self.backup_id = backup_id
|
43
|
+
|
44
|
+
def execute(self) -> Dict[str, Any]:
|
45
|
+
"""Execute the command."""
|
46
|
+
response = self.http_client.delete(f"backup/{self.backup_id}")
|
47
|
+
return response
|