binalyze-air-sdk 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. binalyze_air/__init__.py +77 -77
  2. binalyze_air/apis/__init__.py +67 -27
  3. binalyze_air/apis/acquisitions.py +107 -0
  4. binalyze_air/apis/api_tokens.py +49 -0
  5. binalyze_air/apis/assets.py +161 -0
  6. binalyze_air/apis/audit_logs.py +26 -0
  7. binalyze_air/apis/{authentication.py → auth.py} +29 -27
  8. binalyze_air/apis/auto_asset_tags.py +79 -75
  9. binalyze_air/apis/backup.py +177 -0
  10. binalyze_air/apis/baseline.py +46 -0
  11. binalyze_air/apis/cases.py +225 -0
  12. binalyze_air/apis/cloud_forensics.py +116 -0
  13. binalyze_air/apis/event_subscription.py +96 -96
  14. binalyze_air/apis/evidence.py +249 -53
  15. binalyze_air/apis/interact.py +153 -36
  16. binalyze_air/apis/investigation_hub.py +234 -0
  17. binalyze_air/apis/license.py +104 -0
  18. binalyze_air/apis/logger.py +83 -0
  19. binalyze_air/apis/multipart_upload.py +201 -0
  20. binalyze_air/apis/notifications.py +115 -0
  21. binalyze_air/apis/organizations.py +267 -0
  22. binalyze_air/apis/params.py +44 -39
  23. binalyze_air/apis/policies.py +186 -0
  24. binalyze_air/apis/preset_filters.py +79 -0
  25. binalyze_air/apis/recent_activities.py +71 -0
  26. binalyze_air/apis/relay_server.py +104 -0
  27. binalyze_air/apis/settings.py +395 -27
  28. binalyze_air/apis/tasks.py +80 -0
  29. binalyze_air/apis/triage.py +197 -0
  30. binalyze_air/apis/user_management.py +183 -74
  31. binalyze_air/apis/webhook_executions.py +50 -0
  32. binalyze_air/apis/webhooks.py +322 -230
  33. binalyze_air/base.py +207 -133
  34. binalyze_air/client.py +217 -1337
  35. binalyze_air/commands/__init__.py +175 -145
  36. binalyze_air/commands/acquisitions.py +661 -387
  37. binalyze_air/commands/api_tokens.py +55 -0
  38. binalyze_air/commands/assets.py +324 -362
  39. binalyze_air/commands/{authentication.py → auth.py} +36 -36
  40. binalyze_air/commands/auto_asset_tags.py +230 -230
  41. binalyze_air/commands/backup.py +47 -0
  42. binalyze_air/commands/baseline.py +32 -396
  43. binalyze_air/commands/cases.py +609 -602
  44. binalyze_air/commands/cloud_forensics.py +88 -0
  45. binalyze_air/commands/event_subscription.py +101 -101
  46. binalyze_air/commands/evidences.py +918 -988
  47. binalyze_air/commands/interact.py +172 -58
  48. binalyze_air/commands/investigation_hub.py +315 -0
  49. binalyze_air/commands/license.py +183 -0
  50. binalyze_air/commands/logger.py +126 -0
  51. binalyze_air/commands/multipart_upload.py +363 -0
  52. binalyze_air/commands/notifications.py +45 -0
  53. binalyze_air/commands/organizations.py +200 -221
  54. binalyze_air/commands/policies.py +175 -203
  55. binalyze_air/commands/preset_filters.py +55 -0
  56. binalyze_air/commands/recent_activities.py +32 -0
  57. binalyze_air/commands/relay_server.py +144 -0
  58. binalyze_air/commands/settings.py +431 -29
  59. binalyze_air/commands/tasks.py +95 -56
  60. binalyze_air/commands/triage.py +224 -360
  61. binalyze_air/commands/user_management.py +351 -126
  62. binalyze_air/commands/webhook_executions.py +77 -0
  63. binalyze_air/config.py +244 -244
  64. binalyze_air/exceptions.py +49 -49
  65. binalyze_air/http_client.py +426 -305
  66. binalyze_air/models/__init__.py +287 -285
  67. binalyze_air/models/acquisitions.py +365 -250
  68. binalyze_air/models/api_tokens.py +73 -0
  69. binalyze_air/models/assets.py +438 -438
  70. binalyze_air/models/audit.py +247 -272
  71. binalyze_air/models/audit_logs.py +14 -0
  72. binalyze_air/models/{authentication.py → auth.py} +69 -69
  73. binalyze_air/models/auto_asset_tags.py +227 -116
  74. binalyze_air/models/backup.py +138 -0
  75. binalyze_air/models/baseline.py +231 -231
  76. binalyze_air/models/cases.py +275 -275
  77. binalyze_air/models/cloud_forensics.py +145 -0
  78. binalyze_air/models/event_subscription.py +170 -171
  79. binalyze_air/models/evidence.py +65 -65
  80. binalyze_air/models/evidences.py +367 -348
  81. binalyze_air/models/interact.py +266 -135
  82. binalyze_air/models/investigation_hub.py +265 -0
  83. binalyze_air/models/license.py +150 -0
  84. binalyze_air/models/logger.py +83 -0
  85. binalyze_air/models/multipart_upload.py +352 -0
  86. binalyze_air/models/notifications.py +138 -0
  87. binalyze_air/models/organizations.py +293 -293
  88. binalyze_air/models/params.py +153 -127
  89. binalyze_air/models/policies.py +260 -249
  90. binalyze_air/models/preset_filters.py +79 -0
  91. binalyze_air/models/recent_activities.py +70 -0
  92. binalyze_air/models/relay_server.py +121 -0
  93. binalyze_air/models/settings.py +538 -84
  94. binalyze_air/models/tasks.py +215 -149
  95. binalyze_air/models/triage.py +141 -142
  96. binalyze_air/models/user_management.py +200 -97
  97. binalyze_air/models/webhook_executions.py +33 -0
  98. binalyze_air/queries/__init__.py +121 -133
  99. binalyze_air/queries/acquisitions.py +155 -155
  100. binalyze_air/queries/api_tokens.py +46 -0
  101. binalyze_air/queries/assets.py +186 -105
  102. binalyze_air/queries/audit.py +400 -416
  103. binalyze_air/queries/{authentication.py → auth.py} +55 -55
  104. binalyze_air/queries/auto_asset_tags.py +59 -59
  105. binalyze_air/queries/backup.py +66 -0
  106. binalyze_air/queries/baseline.py +21 -185
  107. binalyze_air/queries/cases.py +292 -292
  108. binalyze_air/queries/cloud_forensics.py +137 -0
  109. binalyze_air/queries/event_subscription.py +54 -54
  110. binalyze_air/queries/evidence.py +139 -139
  111. binalyze_air/queries/evidences.py +279 -279
  112. binalyze_air/queries/interact.py +140 -28
  113. binalyze_air/queries/investigation_hub.py +329 -0
  114. binalyze_air/queries/license.py +85 -0
  115. binalyze_air/queries/logger.py +58 -0
  116. binalyze_air/queries/multipart_upload.py +180 -0
  117. binalyze_air/queries/notifications.py +71 -0
  118. binalyze_air/queries/organizations.py +222 -222
  119. binalyze_air/queries/params.py +154 -115
  120. binalyze_air/queries/policies.py +149 -149
  121. binalyze_air/queries/preset_filters.py +60 -0
  122. binalyze_air/queries/recent_activities.py +44 -0
  123. binalyze_air/queries/relay_server.py +42 -0
  124. binalyze_air/queries/settings.py +533 -20
  125. binalyze_air/queries/tasks.py +125 -81
  126. binalyze_air/queries/triage.py +230 -230
  127. binalyze_air/queries/user_management.py +193 -83
  128. binalyze_air/queries/webhook_executions.py +39 -0
  129. binalyze_air_sdk-1.0.3.dist-info/METADATA +752 -0
  130. binalyze_air_sdk-1.0.3.dist-info/RECORD +132 -0
  131. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/WHEEL +1 -1
  132. binalyze_air/apis/endpoints.py +0 -22
  133. binalyze_air/apis/evidences.py +0 -216
  134. binalyze_air/apis/users.py +0 -68
  135. binalyze_air/commands/users.py +0 -101
  136. binalyze_air/models/endpoints.py +0 -76
  137. binalyze_air/models/users.py +0 -82
  138. binalyze_air/queries/endpoints.py +0 -25
  139. binalyze_air/queries/users.py +0 -69
  140. binalyze_air_sdk-1.0.1.dist-info/METADATA +0 -635
  141. binalyze_air_sdk-1.0.1.dist-info/RECORD +0 -82
  142. {binalyze_air_sdk-1.0.1.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,137 @@
1
+ """
2
+ Cloud Forensics queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import Optional, Dict, Any
6
+
7
+ from ..base import Query
8
+ from ..models.cloud_forensics import (
9
+ CloudAccount, CloudAccountsPaginatedResponse, CloudAccountFilter,
10
+ CloudAccountSyncResult, CloudVendorSyncResult, CloudVendor
11
+ )
12
+ from ..http_client import HTTPClient
13
+
14
+
15
+ class ListCloudAccountsQuery(Query[CloudAccountsPaginatedResponse]):
16
+ """Query to list cloud accounts with optional filtering."""
17
+
18
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[CloudAccountFilter] = None):
19
+ self.http_client = http_client
20
+ self.filter_params = filter_params or CloudAccountFilter()
21
+
22
+ def execute(self) -> CloudAccountsPaginatedResponse:
23
+ """Execute the query."""
24
+ params = {}
25
+
26
+ # Add pagination parameters
27
+ if self.filter_params.page_size is not None:
28
+ params["pageSize"] = self.filter_params.page_size
29
+ if self.filter_params.page_number is not None:
30
+ params["pageNumber"] = self.filter_params.page_number
31
+ if self.filter_params.sort_type is not None:
32
+ params["sortType"] = self.filter_params.sort_type
33
+ if self.filter_params.sort_by is not None:
34
+ params["sortBy"] = self.filter_params.sort_by
35
+
36
+ # Add required filter parameters (API requires these)
37
+ # Default to AWS and organization ID 0 if not specified
38
+ cloud_vendor = self.filter_params.cloud_vendor or CloudVendor.AWS
39
+ # Handle both enum and string values - FIXED
40
+ if hasattr(cloud_vendor, 'value'):
41
+ params["filter[cloudVendor]"] = cloud_vendor.value
42
+ else:
43
+ params["filter[cloudVendor]"] = cloud_vendor
44
+
45
+ organization_ids = self.filter_params.organization_ids or [0]
46
+ params["filter[organizationIds]"] = ",".join(map(str, organization_ids))
47
+
48
+ # Add optional filter parameters
49
+ if self.filter_params.search_term is not None:
50
+ params["filter[searchTerm]"] = self.filter_params.search_term
51
+ if self.filter_params.status is not None:
52
+ # Handle both enum and string values - FIXED
53
+ if hasattr(self.filter_params.status, 'value'):
54
+ params["filter[status]"] = self.filter_params.status.value
55
+ else:
56
+ params["filter[status]"] = self.filter_params.status
57
+
58
+ response = self.http_client.get("cloud-forensics/accounts", params=params)
59
+ return CloudAccountsPaginatedResponse(**response["result"])
60
+
61
+
62
+ class GetCloudAccountQuery(Query[CloudAccount]):
63
+ """Query to get a specific cloud account by ID."""
64
+
65
+ def __init__(self, http_client: HTTPClient, account_id: str):
66
+ self.http_client = http_client
67
+ self.account_id = account_id
68
+
69
+ def execute(self) -> CloudAccount:
70
+ """Execute the query."""
71
+ response = self.http_client.get(f"cloud-forensics/accounts/{self.account_id}")
72
+ return CloudAccount(**response["result"])
73
+
74
+
75
+ class ExportCloudAccountsQuery(Query[Dict[str, Any]]):
76
+ """Query to export cloud accounts data."""
77
+
78
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[CloudAccountFilter] = None):
79
+ self.http_client = http_client
80
+ self.filter_params = filter_params or CloudAccountFilter()
81
+
82
+ def execute(self) -> Dict[str, Any]:
83
+ """Execute the query."""
84
+ params = {}
85
+
86
+ # Add required filter parameters (API requires these)
87
+ # Default to AWS and organization ID 0 if not specified
88
+ cloud_vendor = self.filter_params.cloud_vendor or CloudVendor.AWS
89
+ # Handle both enum and string values - FIXED
90
+ if hasattr(cloud_vendor, 'value'):
91
+ params["filter[cloudVendor]"] = cloud_vendor.value
92
+ else:
93
+ params["filter[cloudVendor]"] = cloud_vendor
94
+
95
+ organization_ids = self.filter_params.organization_ids or [0]
96
+ params["filter[organizationIds]"] = ",".join(map(str, organization_ids))
97
+
98
+ # Add optional filter parameters for export
99
+ if self.filter_params.search_term is not None:
100
+ params["filter[searchTerm]"] = self.filter_params.search_term
101
+ if self.filter_params.status is not None:
102
+ # Handle both enum and string values - FIXED
103
+ if hasattr(self.filter_params.status, 'value'):
104
+ params["filter[status]"] = self.filter_params.status.value
105
+ else:
106
+ params["filter[status]"] = self.filter_params.status
107
+
108
+ response = self.http_client.get("cloud-forensics/accounts/export", params=params)
109
+ return response
110
+
111
+
112
+ class SyncCloudAccountQuery(Query[CloudAccountSyncResult]):
113
+ """Query to sync a specific cloud account by ID."""
114
+
115
+ def __init__(self, http_client: HTTPClient, account_id: str):
116
+ self.http_client = http_client
117
+ self.account_id = account_id
118
+
119
+ def execute(self) -> CloudAccountSyncResult:
120
+ """Execute the query."""
121
+ response = self.http_client.get(f"cloud-forensics/accounts/sync/{self.account_id}")
122
+ return CloudAccountSyncResult(**response["result"])
123
+
124
+
125
+ class SyncCloudAccountsByVendorQuery(Query[CloudVendorSyncResult]):
126
+ """Query to sync all cloud accounts by vendor."""
127
+
128
+ def __init__(self, http_client: HTTPClient, cloud_vendor: CloudVendor):
129
+ self.http_client = http_client
130
+ self.cloud_vendor = cloud_vendor
131
+
132
+ def execute(self) -> CloudVendorSyncResult:
133
+ """Execute the query."""
134
+ response = self.http_client.get(f"cloud-forensics/accounts/sync/{self.cloud_vendor.value}/all")
135
+ # Handle case where result might be None
136
+ result_data = response.get("result") or {}
137
+ return CloudVendorSyncResult(**result_data)
@@ -1,55 +1,55 @@
1
- """
2
- Event Subscription queries for the Binalyze AIR SDK.
3
- """
4
-
5
- from typing import List, Optional
6
-
7
- from ..base import Query
8
- from ..models.event_subscription import EventSubscription, EventSubscriptionFilter
9
- from ..http_client import HTTPClient
10
-
11
-
12
- class ListEventSubscriptionsQuery(Query[List[EventSubscription]]):
13
- """Query to list event subscriptions."""
14
-
15
- def __init__(self, http_client: HTTPClient, filter_params: Optional[EventSubscriptionFilter] = None):
16
- self.http_client = http_client
17
- self.filter_params = filter_params or EventSubscriptionFilter()
18
-
19
- def execute(self) -> List[EventSubscription]:
20
- """Execute the query to get event subscriptions."""
21
- params = self.filter_params.to_params()
22
- response = self.http_client.get("event-subscription", params=params)
23
-
24
- entities = response.get("result", {}).get("entities", [])
25
-
26
- # Use Pydantic parsing with proper field aliasing
27
- subscriptions = []
28
- for item in entities:
29
- # Convert id to string as Pydantic expects
30
- if "id" in item:
31
- item["id"] = str(item["id"])
32
- subscriptions.append(EventSubscription.model_validate(item))
33
-
34
- return subscriptions
35
-
36
-
37
- class GetEventSubscriptionQuery(Query[EventSubscription]):
38
- """Query to get a specific event subscription."""
39
-
40
- def __init__(self, http_client: HTTPClient, subscription_id: str):
41
- self.http_client = http_client
42
- self.subscription_id = subscription_id
43
-
44
- def execute(self) -> EventSubscription:
45
- """Execute the query to get a specific event subscription."""
46
- response = self.http_client.get(f"event-subscription/{self.subscription_id}")
47
-
48
- entity_data = response.get("result", {})
49
-
50
- # Convert id to string as Pydantic expects
51
- if "id" in entity_data:
52
- entity_data["id"] = str(entity_data["id"])
53
-
54
- # Use Pydantic parsing with proper field aliasing
1
+ """
2
+ Event Subscription queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.event_subscription import EventSubscription, EventSubscriptionFilter
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class ListEventSubscriptionsQuery(Query[List[EventSubscription]]):
13
+ """Query to list event subscriptions."""
14
+
15
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[EventSubscriptionFilter] = None):
16
+ self.http_client = http_client
17
+ self.filter_params = filter_params or EventSubscriptionFilter()
18
+
19
+ def execute(self) -> List[EventSubscription]:
20
+ """Execute the query to get event subscriptions."""
21
+ params = self.filter_params.to_params()
22
+ response = self.http_client.get("event-subscription", params=params)
23
+
24
+ entities = response.get("result", {}).get("entities", [])
25
+
26
+ # Use Pydantic parsing with proper field aliasing
27
+ subscriptions = []
28
+ for item in entities:
29
+ # Convert id to string as Pydantic expects
30
+ if "id" in item:
31
+ item["id"] = str(item["id"])
32
+ subscriptions.append(EventSubscription.model_validate(item))
33
+
34
+ return subscriptions
35
+
36
+
37
+ class GetEventSubscriptionQuery(Query[EventSubscription]):
38
+ """Query to get a specific event subscription."""
39
+
40
+ def __init__(self, http_client: HTTPClient, subscription_id: str):
41
+ self.http_client = http_client
42
+ self.subscription_id = subscription_id
43
+
44
+ def execute(self) -> EventSubscription:
45
+ """Execute the query to get a specific event subscription."""
46
+ response = self.http_client.get(f"event-subscription/{self.subscription_id}")
47
+
48
+ entity_data = response.get("result", {})
49
+
50
+ # Convert id to string as Pydantic expects
51
+ if "id" in entity_data:
52
+ entity_data["id"] = str(entity_data["id"])
53
+
54
+ # Use Pydantic parsing with proper field aliasing
55
55
  return EventSubscription.model_validate(entity_data)
@@ -1,140 +1,140 @@
1
- """
2
- Evidence-related queries for the Binalyze AIR SDK.
3
- """
4
-
5
- from ..base import Query
6
- from ..models.evidence import EvidencePPC, EvidenceReportFileInfo, EvidenceReport
7
- from ..http_client import HTTPClient
8
-
9
-
10
- class GetEvidencePPCQuery(Query[EvidencePPC]):
11
- """Query to get case evidence PPC."""
12
-
13
- def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
14
- self.http_client = http_client
15
- self.endpoint_id = endpoint_id
16
- self.task_id = task_id
17
-
18
- def execute(self) -> EvidencePPC:
19
- """Execute the get evidence PPC query."""
20
- try:
21
- # Use binary HTTP client for file downloads
22
- response = self.http_client.get_binary(f"evidence/case/ppc/{self.endpoint_id}/{self.task_id}")
23
-
24
- # Extract content information
25
- content_type = response.headers.get('Content-Type', 'application/octet-stream')
26
- filename = response.headers.get('Content-Disposition')
27
- if filename and 'filename=' in filename:
28
- filename = filename.split('filename=')[1].strip('"')
29
- else:
30
- filename = f"ppc_{self.endpoint_id}_{self.task_id}.zip"
31
-
32
- return EvidencePPC(
33
- endpoint_id=self.endpoint_id,
34
- task_id=self.task_id,
35
- content=response.content,
36
- content_type=content_type,
37
- content_length=len(response.content) if response.content else 0,
38
- filename=filename
39
- )
40
-
41
- except Exception as e:
42
- # Re-raise with more specific error information
43
- error_str = str(e)
44
- if "404" in error_str or "not found" in error_str.lower():
45
- raise Exception(f"Evidence PPC not found (HTTP 404): No task(s) found by provided id(s)")
46
- else:
47
- raise Exception(f"Evidence PPC not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")
48
-
49
-
50
- class GetEvidenceReportFileInfoQuery(Query[EvidenceReportFileInfo]):
51
- """Query to get case evidence report file info."""
52
-
53
- def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
54
- self.http_client = http_client
55
- self.endpoint_id = endpoint_id
56
- self.task_id = task_id
57
-
58
- def execute(self) -> EvidenceReportFileInfo:
59
- """Execute the get evidence report file info query."""
60
- try:
61
- response = self.http_client.get(f"evidence/case/report-file-info/{self.endpoint_id}/{self.task_id}")
62
-
63
- if response.get("success"):
64
- file_info_data = response.get("result", {})
65
-
66
- # Map API fields to SDK model fields
67
- return EvidenceReportFileInfo(
68
- endpoint_id=self.endpoint_id,
69
- task_id=self.task_id,
70
- file_name=file_info_data.get("name"),
71
- file_size=file_info_data.get("size"),
72
- created_at=file_info_data.get("timestampResponse"),
73
- status="available" if not file_info_data.get("purged", False) else "purged",
74
- # Additional fields from API
75
- file_path=file_info_data.get("path"),
76
- encoding=file_info_data.get("encoding"),
77
- mime_type=file_info_data.get("mimeType"),
78
- file_hash=file_info_data.get("hash"),
79
- organization_id=file_info_data.get("organizationId"),
80
- is_purged=file_info_data.get("purged", False)
81
- )
82
-
83
- # Handle error response with detailed information from API
84
- errors = response.get("errors", [])
85
- status_code = response.get("statusCode", "Unknown")
86
- error_message = "; ".join(errors) if errors else "Unknown error"
87
-
88
- raise Exception(f"Evidence report file info not found (HTTP {status_code}): {error_message}")
89
-
90
- except Exception as e:
91
- # Check if this is already our formatted exception
92
- if "Evidence report file info not found" in str(e):
93
- raise e
94
-
95
- # Handle HTTP client exceptions and format them consistently
96
- error_str = str(e)
97
- if "404" in error_str or "not found" in error_str.lower():
98
- raise Exception(f"Evidence report file info not found (HTTP 404): No task(s) found by provided id(s)")
99
- else:
100
- raise Exception(f"Evidence report file info not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")
101
-
102
-
103
- class GetEvidenceReportQuery(Query[EvidenceReport]):
104
- """Query to get case evidence report."""
105
-
106
- def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
107
- self.http_client = http_client
108
- self.endpoint_id = endpoint_id
109
- self.task_id = task_id
110
-
111
- def execute(self) -> EvidenceReport:
112
- """Execute the get evidence report query."""
113
- try:
114
- # Use binary HTTP client for file downloads
115
- response = self.http_client.get_binary(f"evidence/case/report/{self.endpoint_id}/{self.task_id}")
116
-
117
- # Extract content information
118
- content_type = response.headers.get('Content-Type', 'application/octet-stream')
119
- filename = response.headers.get('Content-Disposition')
120
- if filename and 'filename=' in filename:
121
- filename = filename.split('filename=')[1].strip('"')
122
- else:
123
- filename = f"report_{self.endpoint_id}_{self.task_id}"
124
-
125
- return EvidenceReport(
126
- endpoint_id=self.endpoint_id,
127
- task_id=self.task_id,
128
- content=response.content,
129
- content_type=content_type,
130
- content_length=len(response.content) if response.content else 0,
131
- filename=filename
132
- )
133
-
134
- except Exception as e:
135
- # Re-raise with more specific error information
136
- error_str = str(e)
137
- if "404" in error_str or "not found" in error_str.lower():
138
- raise Exception(f"Evidence report not found (HTTP 404): No task(s) found by provided id(s)")
139
- else:
1
+ """
2
+ Evidence-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from ..base import Query
6
+ from ..models.evidence import EvidencePPC, EvidenceReportFileInfo, EvidenceReport
7
+ from ..http_client import HTTPClient
8
+
9
+
10
+ class GetEvidencePPCQuery(Query[EvidencePPC]):
11
+ """Query to get case evidence PPC."""
12
+
13
+ def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
14
+ self.http_client = http_client
15
+ self.endpoint_id = endpoint_id
16
+ self.task_id = task_id
17
+
18
+ def execute(self) -> EvidencePPC:
19
+ """Execute the get evidence PPC query."""
20
+ try:
21
+ # Use binary HTTP client for file downloads
22
+ response = self.http_client.get_binary(f"evidence/case/ppc/{self.endpoint_id}/{self.task_id}")
23
+
24
+ # Extract content information
25
+ content_type = response.headers.get('Content-Type', 'application/octet-stream')
26
+ filename = response.headers.get('Content-Disposition')
27
+ if filename and 'filename=' in filename:
28
+ filename = filename.split('filename=')[1].strip('"')
29
+ else:
30
+ filename = f"ppc_{self.endpoint_id}_{self.task_id}.zip"
31
+
32
+ return EvidencePPC(
33
+ endpoint_id=self.endpoint_id,
34
+ task_id=self.task_id,
35
+ content=response.content,
36
+ content_type=content_type,
37
+ content_length=len(response.content) if response.content else 0,
38
+ filename=filename
39
+ )
40
+
41
+ except Exception as e:
42
+ # Re-raise with more specific error information
43
+ error_str = str(e)
44
+ if "404" in error_str or "not found" in error_str.lower():
45
+ raise Exception(f"Evidence PPC not found (HTTP 404): No task(s) found by provided id(s)")
46
+ else:
47
+ raise Exception(f"Evidence PPC not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")
48
+
49
+
50
+ class GetEvidenceReportFileInfoQuery(Query[EvidenceReportFileInfo]):
51
+ """Query to get case evidence report file info."""
52
+
53
+ def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
54
+ self.http_client = http_client
55
+ self.endpoint_id = endpoint_id
56
+ self.task_id = task_id
57
+
58
+ def execute(self) -> EvidenceReportFileInfo:
59
+ """Execute the get evidence report file info query."""
60
+ try:
61
+ response = self.http_client.get(f"evidence/case/report-file-info/{self.endpoint_id}/{self.task_id}")
62
+
63
+ if response.get("success"):
64
+ file_info_data = response.get("result", {})
65
+
66
+ # Map API fields to SDK model fields
67
+ return EvidenceReportFileInfo(
68
+ endpoint_id=self.endpoint_id,
69
+ task_id=self.task_id,
70
+ file_name=file_info_data.get("name"),
71
+ file_size=file_info_data.get("size"),
72
+ created_at=file_info_data.get("timestampResponse"),
73
+ status="available" if not file_info_data.get("purged", False) else "purged",
74
+ # Additional fields from API
75
+ file_path=file_info_data.get("path"),
76
+ encoding=file_info_data.get("encoding"),
77
+ mime_type=file_info_data.get("mimeType"),
78
+ file_hash=file_info_data.get("hash"),
79
+ organization_id=file_info_data.get("organizationId"),
80
+ is_purged=file_info_data.get("purged", False)
81
+ )
82
+
83
+ # Handle error response with detailed information from API
84
+ errors = response.get("errors", [])
85
+ status_code = response.get("statusCode", "Unknown")
86
+ error_message = "; ".join(errors) if errors else "Unknown error"
87
+
88
+ raise Exception(f"Evidence report file info not found (HTTP {status_code}): {error_message}")
89
+
90
+ except Exception as e:
91
+ # Check if this is already our formatted exception
92
+ if "Evidence report file info not found" in str(e):
93
+ raise e
94
+
95
+ # Handle HTTP client exceptions and format them consistently
96
+ error_str = str(e)
97
+ if "404" in error_str or "not found" in error_str.lower():
98
+ raise Exception(f"Evidence report file info not found (HTTP 404): No task(s) found by provided id(s)")
99
+ else:
100
+ raise Exception(f"Evidence report file info not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")
101
+
102
+
103
+ class GetEvidenceReportQuery(Query[EvidenceReport]):
104
+ """Query to get case evidence report."""
105
+
106
+ def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
107
+ self.http_client = http_client
108
+ self.endpoint_id = endpoint_id
109
+ self.task_id = task_id
110
+
111
+ def execute(self) -> EvidenceReport:
112
+ """Execute the get evidence report query."""
113
+ try:
114
+ # Use binary HTTP client for file downloads
115
+ response = self.http_client.get_binary(f"evidence/case/report/{self.endpoint_id}/{self.task_id}")
116
+
117
+ # Extract content information
118
+ content_type = response.headers.get('Content-Type', 'application/octet-stream')
119
+ filename = response.headers.get('Content-Disposition')
120
+ if filename and 'filename=' in filename:
121
+ filename = filename.split('filename=')[1].strip('"')
122
+ else:
123
+ filename = f"report_{self.endpoint_id}_{self.task_id}"
124
+
125
+ return EvidenceReport(
126
+ endpoint_id=self.endpoint_id,
127
+ task_id=self.task_id,
128
+ content=response.content,
129
+ content_type=content_type,
130
+ content_length=len(response.content) if response.content else 0,
131
+ filename=filename
132
+ )
133
+
134
+ except Exception as e:
135
+ # Re-raise with more specific error information
136
+ error_str = str(e)
137
+ if "404" in error_str or "not found" in error_str.lower():
138
+ raise Exception(f"Evidence report not found (HTTP 404): No task(s) found by provided id(s)")
139
+ else:
140
140
  raise Exception(f"Evidence report not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")