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,56 @@
1
+ """
2
+ Authentication-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from ..base import Query
6
+ from ..models.authentication import AuthStatus, User, UserProfile, UserRole
7
+ from ..http_client import HTTPClient
8
+
9
+
10
+ class CheckAuthStatusQuery(Query[AuthStatus]):
11
+ """Query to check authentication status."""
12
+
13
+ def __init__(self, http_client: HTTPClient):
14
+ self.http_client = http_client
15
+
16
+ def execute(self) -> AuthStatus:
17
+ """Execute the auth status check query."""
18
+ # Try different endpoint patterns since auth endpoints may vary
19
+ endpoints_to_try = [
20
+ "auth/check", # Original pattern
21
+ # Note: /api/public/auth/check may not exist in all API versions
22
+ # The HTTP client will add the api_prefix automatically
23
+ ]
24
+
25
+ last_exception = None
26
+
27
+ for endpoint in endpoints_to_try:
28
+ try:
29
+ response = self.http_client.get(endpoint)
30
+
31
+ if response.get("success"):
32
+ result = response.get("result", {})
33
+
34
+ # Use Pydantic to parse the User data with proper field aliasing
35
+ user = User.model_validate(result)
36
+ return AuthStatus(authenticated=True, user=user)
37
+ else:
38
+ # Return unauthenticated status for failed responses
39
+ return AuthStatus(authenticated=False)
40
+
41
+ except Exception as e:
42
+ last_exception = e
43
+ continue # Try next endpoint
44
+
45
+ # If all endpoints failed, check if it's because auth features are disabled
46
+ if last_exception and "auth-management-via-api" in str(last_exception):
47
+ # Auth features are disabled - this is a configuration issue, not authentication failure
48
+ # We can't determine auth status, so return a special state
49
+ return AuthStatus(authenticated=False)
50
+
51
+ # If we get here, all endpoints failed
52
+ if last_exception:
53
+ raise last_exception
54
+
55
+ # Return unauthenticated status as fallback
56
+ return AuthStatus(authenticated=False)
@@ -0,0 +1,60 @@
1
+ """
2
+ Auto Asset Tags-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.auto_asset_tags import AutoAssetTag, AutoAssetTagFilter
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class ListAutoAssetTagsQuery(Query[List[AutoAssetTag]]):
13
+ """Query to list auto asset tags."""
14
+
15
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[AutoAssetTagFilter] = None):
16
+ self.http_client = http_client
17
+ self.filter_params = filter_params
18
+
19
+ def execute(self) -> List[AutoAssetTag]:
20
+ """Execute the list auto asset tags query."""
21
+ params = {}
22
+
23
+ # Add default organization filtering if no filter is provided
24
+ if self.filter_params:
25
+ params = self.filter_params.to_params()
26
+ else:
27
+ # Default organization filtering - critical for the API to work
28
+ params["filter[organizationIds]"] = "0"
29
+
30
+ # Ensure organization filtering is always present
31
+ if "filter[organizationIds]" not in params and "filter[organizationId]" not in params:
32
+ params["filter[organizationIds]"] = "0"
33
+
34
+ response = self.http_client.get("auto-asset-tag", params=params)
35
+
36
+ if response.get("success"):
37
+ tags_data = response.get("result", {}).get("entities", [])
38
+ # Use Pydantic parsing with proper field aliasing
39
+ return [AutoAssetTag.model_validate(tag_data) for tag_data in tags_data]
40
+
41
+ return []
42
+
43
+
44
+ class GetAutoAssetTagQuery(Query[AutoAssetTag]):
45
+ """Query to get auto asset tag by ID."""
46
+
47
+ def __init__(self, http_client: HTTPClient, tag_id: str):
48
+ self.http_client = http_client
49
+ self.tag_id = tag_id
50
+
51
+ def execute(self) -> AutoAssetTag:
52
+ """Execute the get auto asset tag query."""
53
+ response = self.http_client.get(f"auto-asset-tag/{self.tag_id}")
54
+
55
+ if response.get("success"):
56
+ tag_data = response.get("result", {})
57
+ # Use Pydantic parsing with proper field aliasing
58
+ return AutoAssetTag.model_validate(tag_data)
59
+
60
+ raise Exception(f"Auto asset tag not found: {self.tag_id}")
@@ -0,0 +1,185 @@
1
+ """
2
+ Baseline-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.baseline import (
9
+ Baseline, BaselineProfile, BaselineComparison, BaselineChange,
10
+ BaselineSchedule, BaselineFilter, BaselineStatus, ComparisonStatus
11
+ )
12
+ from ..http_client import HTTPClient
13
+
14
+
15
+ class ListBaselinesQuery(Query[List[Baseline]]):
16
+ """Query to list baselines with optional filtering."""
17
+
18
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[BaselineFilter] = None, organization_ids: Optional[List[int]] = None):
19
+ self.http_client = http_client
20
+ self.filter_params = filter_params or BaselineFilter()
21
+ self.organization_ids = organization_ids or [0]
22
+
23
+ def execute(self) -> List[Baseline]:
24
+ """Execute the query to list baselines."""
25
+ params = {}
26
+ if self.filter_params:
27
+ params = self.filter_params.model_dump(exclude_none=True)
28
+
29
+ response = self.http_client.get("baselines", params=params)
30
+
31
+ if response.get("success"):
32
+ baselines_data = response.get("result", {}).get("entities", [])
33
+ # Use Pydantic parsing with proper field aliasing
34
+ return [Baseline.model_validate(baseline_data) for baseline_data in baselines_data]
35
+
36
+ return []
37
+
38
+
39
+ class GetBaselineQuery(Query[Baseline]):
40
+ """Query to get a specific baseline by ID."""
41
+
42
+ def __init__(self, http_client: HTTPClient, baseline_id: str):
43
+ self.http_client = http_client
44
+ self.baseline_id = baseline_id
45
+
46
+ def execute(self) -> Baseline:
47
+ """Execute the query to get baseline details."""
48
+ response = self.http_client.get(f"baselines/{self.baseline_id}")
49
+
50
+ if response.get("success"):
51
+ baseline_data = response.get("result", {})
52
+ # Use Pydantic parsing with proper field aliasing
53
+ return Baseline.model_validate(baseline_data)
54
+
55
+ raise Exception(f"Baseline not found: {self.baseline_id}")
56
+
57
+
58
+ class GetBaselineComparisonsQuery(Query[List[BaselineComparison]]):
59
+ """Query to get baseline comparisons."""
60
+
61
+ def __init__(self, http_client: HTTPClient, baseline_id: str):
62
+ self.http_client = http_client
63
+ self.baseline_id = baseline_id
64
+
65
+ def execute(self) -> List[BaselineComparison]:
66
+ """Execute the query to get baseline comparisons."""
67
+ response = self.http_client.get(f"baselines/{self.baseline_id}/comparisons")
68
+
69
+ if response.get("success"):
70
+ comparisons_data = response.get("result", {}).get("entities", [])
71
+ comparisons = []
72
+ for comparison_data in comparisons_data:
73
+ # Parse changes if present
74
+ changes = []
75
+ for change_data in comparison_data.get("changes", []):
76
+ # Use Pydantic parsing for changes
77
+ changes.append(BaselineChange.model_validate(change_data))
78
+
79
+ # Add parsed changes to comparison data
80
+ comparison_data["changes"] = changes
81
+
82
+ # Use Pydantic parsing with proper field aliasing
83
+ comparisons.append(BaselineComparison.model_validate(comparison_data))
84
+
85
+ return comparisons
86
+
87
+ return []
88
+
89
+
90
+ class GetBaselineComparisonQuery(Query[BaselineComparison]):
91
+ """Query to get a specific baseline comparison by ID."""
92
+
93
+ def __init__(self, http_client: HTTPClient, comparison_id: str):
94
+ self.http_client = http_client
95
+ self.comparison_id = comparison_id
96
+
97
+ def execute(self) -> BaselineComparison:
98
+ """Execute the query to get baseline comparison details."""
99
+ response = self.http_client.get(f"baselines/comparisons/{self.comparison_id}")
100
+
101
+ if response.get("success"):
102
+ comparison_data = response.get("result", {})
103
+
104
+ # Parse changes if present
105
+ changes = []
106
+ for change_data in comparison_data.get("changes", []):
107
+ # Use Pydantic parsing for changes
108
+ changes.append(BaselineChange.model_validate(change_data))
109
+
110
+ # Add parsed changes to comparison data
111
+ comparison_data["changes"] = changes
112
+
113
+ # Use Pydantic parsing with proper field aliasing
114
+ return BaselineComparison.model_validate(comparison_data)
115
+
116
+ raise Exception(f"Baseline comparison not found: {self.comparison_id}")
117
+
118
+
119
+ class ListBaselineProfilesQuery(Query[List[BaselineProfile]]):
120
+ """Query to list baseline profiles."""
121
+
122
+ def __init__(self, http_client: HTTPClient, organization_ids: Optional[List[int]] = None):
123
+ self.http_client = http_client
124
+ self.organization_ids = organization_ids or [0]
125
+
126
+ def execute(self) -> List[BaselineProfile]:
127
+ """Execute the query to list baseline profiles."""
128
+ params = {
129
+ "filter[organizationIds]": ",".join(map(str, self.organization_ids))
130
+ }
131
+
132
+ response = self.http_client.get("baseline-profiles", params=params)
133
+
134
+ if response.get("success"):
135
+ profiles_data = response.get("result", {}).get("entities", [])
136
+ # Use Pydantic parsing with proper field aliasing
137
+ return [BaselineProfile.model_validate(profile_data) for profile_data in profiles_data]
138
+
139
+ return []
140
+
141
+
142
+ class GetBaselineProfileQuery(Query[BaselineProfile]):
143
+ """Query to get a specific baseline profile by ID."""
144
+
145
+ def __init__(self, http_client: HTTPClient, profile_id: str):
146
+ self.http_client = http_client
147
+ self.profile_id = profile_id
148
+
149
+ def execute(self) -> BaselineProfile:
150
+ """Execute the query to get baseline profile details."""
151
+ response = self.http_client.get(f"baseline-profiles/{self.profile_id}")
152
+
153
+ if response.get("success"):
154
+ profile_data = response.get("result", {})
155
+ # Use Pydantic parsing with proper field aliasing
156
+ return BaselineProfile.model_validate(profile_data)
157
+
158
+ raise Exception(f"Baseline profile not found: {self.profile_id}")
159
+
160
+
161
+ class GetBaselineSchedulesQuery(Query[List[BaselineSchedule]]):
162
+ """Query to get baseline schedules."""
163
+
164
+ def __init__(self, http_client: HTTPClient, baseline_id: Optional[str] = None, organization_ids: Optional[List[int]] = None):
165
+ self.http_client = http_client
166
+ self.baseline_id = baseline_id
167
+ self.organization_ids = organization_ids or [0]
168
+
169
+ def execute(self) -> List[BaselineSchedule]:
170
+ """Execute the query to get baseline schedules."""
171
+ params = {
172
+ "filter[organizationIds]": ",".join(map(str, self.organization_ids))
173
+ }
174
+
175
+ if self.baseline_id:
176
+ params["baselineId"] = self.baseline_id
177
+
178
+ response = self.http_client.get("baseline-schedules", params=params)
179
+
180
+ if response.get("success"):
181
+ schedules_data = response.get("result", {}).get("entities", [])
182
+ # Use Pydantic parsing with proper field aliasing
183
+ return [BaselineSchedule.model_validate(schedule_data) for schedule_data in schedules_data]
184
+
185
+ return []
@@ -0,0 +1,293 @@
1
+ """
2
+ Case-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.cases import (
9
+ Case, CaseActivity, CaseEndpoint, CaseTask, User, CaseFilter, CaseActivityFilter,
10
+ CaseEndpointFilter, CaseTaskFilter, CaseUserFilter
11
+ )
12
+ from ..http_client import HTTPClient
13
+
14
+
15
+ class ListCasesQuery(Query[List[Case]]):
16
+ """Query to list cases with optional filtering."""
17
+
18
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[CaseFilter] = None, organization_ids: Optional[List[int]] = None):
19
+ self.http_client = http_client
20
+ self.filter_params = filter_params or CaseFilter()
21
+ self.organization_ids = organization_ids or [0]
22
+
23
+ def execute(self) -> List[Case]:
24
+ """Execute the query to list cases."""
25
+ params = self.filter_params.to_params()
26
+
27
+ # Add organization IDs
28
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
29
+
30
+ # Ensure consistent sorting to match API defaults
31
+ if "sortBy" not in params:
32
+ params["sortBy"] = "createdAt"
33
+ if "sortType" not in params:
34
+ params["sortType"] = "ASC"
35
+
36
+ response = self.http_client.get("cases", params=params)
37
+
38
+ entities = response.get("result", {}).get("entities", [])
39
+
40
+ # Use Pydantic parsing with proper field aliasing
41
+ return [Case.model_validate(entity_data) for entity_data in entities]
42
+
43
+
44
+ class GetCaseQuery(Query[Case]):
45
+ """Query to get a specific case by ID."""
46
+
47
+ def __init__(self, http_client: HTTPClient, case_id: str):
48
+ self.http_client = http_client
49
+ self.case_id = case_id
50
+
51
+ def execute(self) -> Case:
52
+ """Execute the query to get case details."""
53
+ response = self.http_client.get(f"cases/{self.case_id}")
54
+
55
+ entity_data = response.get("result", {})
56
+
57
+ # Use Pydantic parsing with proper field aliasing
58
+ return Case.model_validate(entity_data)
59
+
60
+
61
+ class GetCaseActivitiesQuery(Query[List[CaseActivity]]):
62
+ """Query to get activities for a specific case."""
63
+
64
+ def __init__(self, http_client: HTTPClient, case_id: str, filter_params: Optional[CaseActivityFilter] = None):
65
+ self.http_client = http_client
66
+ self.case_id = case_id
67
+ self.filter_params = filter_params or CaseActivityFilter()
68
+
69
+ def execute(self) -> List[CaseActivity]:
70
+ """Execute the query to get case activities."""
71
+ params = {}
72
+
73
+ # Add pagination parameters
74
+ if self.filter_params.page_number is not None:
75
+ params["pageNumber"] = self.filter_params.page_number
76
+ if self.filter_params.page_size is not None:
77
+ params["pageSize"] = self.filter_params.page_size
78
+
79
+ # Add sorting parameters
80
+ if self.filter_params.sort_by is not None:
81
+ params["sortBy"] = self.filter_params.sort_by
82
+ if self.filter_params.sort_type is not None:
83
+ params["sortType"] = self.filter_params.sort_type
84
+
85
+ # Add filter parameters
86
+ if self.filter_params.performed_by is not None:
87
+ params["filter[performedBy]"] = ",".join(self.filter_params.performed_by)
88
+ if self.filter_params.types is not None:
89
+ params["filter[types]"] = ",".join(self.filter_params.types)
90
+ if self.filter_params.search_term is not None:
91
+ params["filter[searchTerm]"] = self.filter_params.search_term
92
+ if self.filter_params.occurred_at is not None:
93
+ params["filter[occurredAt]"] = self.filter_params.occurred_at
94
+
95
+ response = self.http_client.get(f"cases/{self.case_id}/activities", params=params)
96
+
97
+ entities = response.get("result", {}).get("entities", [])
98
+
99
+ # Use Pydantic parsing with proper field aliasing
100
+ return [CaseActivity.model_validate(entity_data) for entity_data in entities]
101
+
102
+
103
+ class GetCaseEndpointsQuery(Query[List[CaseEndpoint]]):
104
+ """Query to get endpoints for a specific case."""
105
+
106
+ def __init__(self, http_client: HTTPClient, case_id: str, filter_params: Optional[CaseEndpointFilter] = None):
107
+ self.http_client = http_client
108
+ self.case_id = case_id
109
+ self.filter_params = filter_params or CaseEndpointFilter()
110
+
111
+ def execute(self) -> List[CaseEndpoint]:
112
+ """Execute the query to get case endpoints."""
113
+ params = {}
114
+
115
+ # Add pagination parameters
116
+ if self.filter_params.page_number is not None:
117
+ params["pageNumber"] = self.filter_params.page_number
118
+ if self.filter_params.page_size is not None:
119
+ params["pageSize"] = self.filter_params.page_size
120
+
121
+ # Add sorting parameters
122
+ if self.filter_params.sort_by is not None:
123
+ params["sortBy"] = self.filter_params.sort_by
124
+ if self.filter_params.sort_type is not None:
125
+ params["sortType"] = self.filter_params.sort_type
126
+
127
+ # Add filter parameters
128
+ if self.filter_params.organization_ids is not None:
129
+ params["filter[organizationIds]"] = ",".join(map(str, self.filter_params.organization_ids))
130
+ else:
131
+ # Default to organization 0 if not specified
132
+ params["filter[organizationIds]"] = "0"
133
+
134
+ if self.filter_params.search_term is not None:
135
+ params["filter[searchTerm]"] = self.filter_params.search_term
136
+ if self.filter_params.name is not None:
137
+ params["filter[name]"] = self.filter_params.name
138
+ if self.filter_params.ip_address is not None:
139
+ params["filter[ipAddress]"] = self.filter_params.ip_address
140
+ if self.filter_params.group_id is not None:
141
+ params["filter[groupId]"] = self.filter_params.group_id
142
+ if self.filter_params.group_full_path is not None:
143
+ params["filter[groupFullPath]"] = self.filter_params.group_full_path
144
+ if self.filter_params.label is not None:
145
+ params["filter[label]"] = self.filter_params.label
146
+ if self.filter_params.last_seen is not None:
147
+ params["filter[lastSeen]"] = self.filter_params.last_seen
148
+ if self.filter_params.managed_status is not None:
149
+ params["filter[managedStatus]"] = ",".join(self.filter_params.managed_status)
150
+ if self.filter_params.isolation_status is not None:
151
+ params["filter[isolationStatus]"] = ",".join(self.filter_params.isolation_status)
152
+ if self.filter_params.platform is not None:
153
+ params["filter[platform]"] = ",".join(self.filter_params.platform)
154
+ if self.filter_params.issue is not None:
155
+ params["filter[issue]"] = ",".join(self.filter_params.issue)
156
+ if self.filter_params.online_status is not None:
157
+ params["filter[onlineStatus]"] = ",".join(self.filter_params.online_status)
158
+ if self.filter_params.tags is not None:
159
+ params["filter[tags]"] = ",".join(self.filter_params.tags)
160
+ if self.filter_params.version is not None:
161
+ params["filter[version]"] = self.filter_params.version
162
+ if self.filter_params.policy is not None:
163
+ params["filter[policy]"] = self.filter_params.policy
164
+ if self.filter_params.included_endpoint_ids is not None:
165
+ params["filter[includedEndpointIds]"] = ",".join(self.filter_params.included_endpoint_ids)
166
+ if self.filter_params.excluded_endpoint_ids is not None:
167
+ params["filter[excludedEndpointIds]"] = ",".join(self.filter_params.excluded_endpoint_ids)
168
+ if self.filter_params.aws_regions is not None:
169
+ params["filter[awsRegions]"] = ",".join(self.filter_params.aws_regions)
170
+ if self.filter_params.azure_regions is not None:
171
+ params["filter[azureRegions]"] = ",".join(self.filter_params.azure_regions)
172
+
173
+ response = self.http_client.get(f"cases/{self.case_id}/endpoints", params=params)
174
+
175
+ entities = response.get("result", {}).get("entities", [])
176
+
177
+ # Use Pydantic parsing with proper field aliasing
178
+ return [CaseEndpoint.model_validate(entity_data) for entity_data in entities]
179
+
180
+
181
+ class GetCaseTasksQuery(Query[List[CaseTask]]):
182
+ """Query to get tasks for a specific case."""
183
+
184
+ def __init__(self, http_client: HTTPClient, case_id: str, filter_params: Optional[CaseTaskFilter] = None):
185
+ self.http_client = http_client
186
+ self.case_id = case_id
187
+ self.filter_params = filter_params or CaseTaskFilter()
188
+
189
+ def execute(self) -> List[CaseTask]:
190
+ """Execute the query to get case tasks."""
191
+ params = {}
192
+
193
+ # Add pagination parameters
194
+ if self.filter_params.page_number is not None:
195
+ params["pageNumber"] = self.filter_params.page_number
196
+ if self.filter_params.page_size is not None:
197
+ params["pageSize"] = self.filter_params.page_size
198
+
199
+ # Add sorting parameters
200
+ if self.filter_params.sort_by is not None:
201
+ params["sortBy"] = self.filter_params.sort_by
202
+ if self.filter_params.sort_type is not None:
203
+ params["sortType"] = self.filter_params.sort_type
204
+
205
+ # Add filter parameters
206
+ if self.filter_params.organization_ids is not None:
207
+ params["filter[organizationIds]"] = ",".join(map(str, self.filter_params.organization_ids))
208
+ else:
209
+ # Default to organization 0 if not specified
210
+ params["filter[organizationIds]"] = "0"
211
+
212
+ if self.filter_params.search_term is not None:
213
+ params["filter[searchTerm]"] = self.filter_params.search_term
214
+ if self.filter_params.name is not None:
215
+ params["filter[name]"] = self.filter_params.name
216
+ if self.filter_params.endpoint_ids is not None:
217
+ params["filter[endpointIds]"] = ",".join(self.filter_params.endpoint_ids)
218
+ if self.filter_params.execution_type is not None:
219
+ params["filter[executionType]"] = self.filter_params.execution_type
220
+ if self.filter_params.status is not None:
221
+ params["filter[status]"] = self.filter_params.status
222
+ if self.filter_params.type is not None:
223
+ params["filter[type]"] = self.filter_params.type
224
+ if self.filter_params.asset_names is not None:
225
+ params["filter[assetNames]"] = self.filter_params.asset_names
226
+ if self.filter_params.started_by is not None:
227
+ params["filter[startedBy]"] = self.filter_params.started_by
228
+
229
+ response = self.http_client.get(f"cases/{self.case_id}/tasks", params=params)
230
+
231
+ entities = response.get("result", {}).get("entities", [])
232
+
233
+ # Use Pydantic parsing with proper field aliasing
234
+ return [CaseTask.model_validate(entity_data) for entity_data in entities]
235
+
236
+
237
+ class GetCaseUsersQuery(Query[List[User]]):
238
+ """Query to get users for a specific case."""
239
+
240
+ def __init__(self, http_client: HTTPClient, case_id: str, filter_params: Optional[CaseUserFilter] = None):
241
+ self.http_client = http_client
242
+ self.case_id = case_id
243
+ self.filter_params = filter_params or CaseUserFilter()
244
+
245
+ def execute(self) -> List[User]:
246
+ """Execute the query to get case users."""
247
+ params = {}
248
+
249
+ # Add pagination parameters
250
+ if self.filter_params.page_number is not None:
251
+ params["pageNumber"] = self.filter_params.page_number
252
+ if self.filter_params.page_size is not None:
253
+ params["pageSize"] = self.filter_params.page_size
254
+
255
+ # Add sorting parameters
256
+ if self.filter_params.sort_by is not None:
257
+ params["sortBy"] = self.filter_params.sort_by
258
+ if self.filter_params.sort_type is not None:
259
+ params["sortType"] = self.filter_params.sort_type
260
+
261
+ # Add filter parameters
262
+ if self.filter_params.organization_ids is not None:
263
+ params["filter[organizationIds]"] = ",".join(map(str, self.filter_params.organization_ids))
264
+ else:
265
+ # Default to organization 0 if not specified
266
+ params["filter[organizationIds]"] = "0"
267
+
268
+ if self.filter_params.search_term is not None:
269
+ params["filter[searchTerm]"] = self.filter_params.search_term
270
+
271
+ response = self.http_client.get(f"cases/{self.case_id}/users", params=params)
272
+
273
+ entities = response.get("result", {}).get("entities", [])
274
+
275
+ # Use Pydantic parsing with proper field aliasing
276
+ return [User.model_validate(entity_data) for entity_data in entities]
277
+
278
+
279
+ class CheckCaseNameQuery(Query[bool]):
280
+ """Query to check if a case name is available."""
281
+
282
+ def __init__(self, http_client: HTTPClient, name: str):
283
+ self.http_client = http_client
284
+ self.name = name
285
+
286
+ def execute(self) -> bool:
287
+ """Execute the query to check case name availability."""
288
+ params = {"name": self.name}
289
+
290
+ response = self.http_client.get("cases/check", params=params)
291
+
292
+ # Return the actual result (whether name is taken/available)
293
+ return response.get("result", False)
@@ -0,0 +1,25 @@
1
+ """
2
+ Endpoints queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.endpoints import EndpointTag, EndpointTagFilter
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class GetEndpointTagsQuery(Query[List[EndpointTag]]):
13
+ """Query to get endpoint tags."""
14
+
15
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[EndpointTagFilter] = None):
16
+ self.http_client = http_client
17
+ self.filter_params = filter_params or EndpointTagFilter()
18
+
19
+ def execute(self) -> List[EndpointTag]:
20
+ """Execute the query to get endpoint tags."""
21
+ params = self.filter_params.to_params()
22
+ response = self.http_client.get("endpoints/tags", params=params)
23
+
24
+ entities = response.get("result", [])
25
+ return [EndpointTag(**item) for item in entities]
@@ -0,0 +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
55
+ return EventSubscription.model_validate(entity_data)