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,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:
140
+ raise Exception(f"Evidence report not found for endpoint {self.endpoint_id}, task {self.task_id}: {error_str}")
@@ -0,0 +1,280 @@
1
+ """
2
+ Evidences/Repositories-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.evidences import (
9
+ EvidenceRepository, AmazonS3Repository, AzureStorageRepository,
10
+ FTPSRepository, SFTPRepository, SMBRepository, RepositoryFilter
11
+ )
12
+ from ..http_client import HTTPClient
13
+
14
+
15
+ class ListRepositoriesQuery(Query[List[EvidenceRepository]]):
16
+ """Query to list evidence repositories."""
17
+
18
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None, organization_ids: Optional[List[int]] = None):
19
+ self.http_client = http_client
20
+ self.filter_params = filter_params
21
+ self.organization_ids = organization_ids or [0]
22
+
23
+ def execute(self) -> List[EvidenceRepository]:
24
+ """Execute the list repositories query."""
25
+ params = {}
26
+ if self.filter_params:
27
+ params = self.filter_params.to_params()
28
+ else:
29
+ # Add required organization IDs filter if no filter provided
30
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
31
+
32
+ # Ensure consistent sorting to match API defaults
33
+ if "sortBy" not in params:
34
+ params["sortBy"] = "createdAt"
35
+ if "sortType" not in params:
36
+ params["sortType"] = "ASC"
37
+
38
+ response = self.http_client.get("evidences/repositories", params=params)
39
+
40
+ if response.get("success"):
41
+ repositories_data = response.get("result", {}).get("entities", [])
42
+
43
+ # Use Pydantic parsing with proper field aliasing
44
+ repositories = []
45
+ for repo_data in repositories_data:
46
+ repositories.append(EvidenceRepository.model_validate(repo_data))
47
+
48
+ return repositories
49
+
50
+ return []
51
+
52
+
53
+ class GetRepositoryQuery(Query[EvidenceRepository]):
54
+ """Query to get evidence repository by ID."""
55
+
56
+ def __init__(self, http_client: HTTPClient, repository_id: str):
57
+ self.http_client = http_client
58
+ self.repository_id = repository_id
59
+
60
+ def execute(self) -> EvidenceRepository:
61
+ """Execute the get repository query."""
62
+ response = self.http_client.get(f"evidences/repositories/{self.repository_id}")
63
+
64
+ if response.get("success"):
65
+ repository_data = response.get("result", {})
66
+
67
+ # Use Pydantic parsing with proper field aliasing
68
+ return EvidenceRepository.model_validate(repository_data)
69
+
70
+ raise Exception(f"Evidence repository not found: {self.repository_id}")
71
+
72
+
73
+ # Amazon S3 Repository Queries
74
+
75
+ class ListAmazonS3RepositoriesQuery(Query[List[AmazonS3Repository]]):
76
+ """Query to list Amazon S3 repositories."""
77
+
78
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None):
79
+ self.http_client = http_client
80
+ self.filter_params = filter_params
81
+
82
+ def execute(self) -> List[AmazonS3Repository]:
83
+ """Execute the list Amazon S3 repositories query."""
84
+ params = {}
85
+ if self.filter_params:
86
+ params = self.filter_params.model_dump(exclude_none=True)
87
+
88
+ response = self.http_client.get("evidences/repositories/amazon-s3", params=params)
89
+
90
+ if response.get("success"):
91
+ repositories_data = response.get("result", {}).get("entities", [])
92
+ return [AmazonS3Repository(**repo) for repo in repositories_data]
93
+
94
+ return []
95
+
96
+
97
+ class GetAmazonS3RepositoryQuery(Query[AmazonS3Repository]):
98
+ """Query to get Amazon S3 repository by ID."""
99
+
100
+ def __init__(self, http_client: HTTPClient, repository_id: str):
101
+ self.http_client = http_client
102
+ self.repository_id = repository_id
103
+
104
+ def execute(self) -> AmazonS3Repository:
105
+ """Execute the get Amazon S3 repository query."""
106
+ response = self.http_client.get(f"evidences/repositories/amazon-s3/{self.repository_id}")
107
+
108
+ if response.get("success"):
109
+ repository_data = response.get("result", {})
110
+ return AmazonS3Repository(**repository_data)
111
+
112
+ raise Exception(f"Amazon S3 repository not found: {self.repository_id}")
113
+
114
+
115
+ # Azure Storage Repository Queries
116
+
117
+ class ListAzureStorageRepositoriesQuery(Query[List[AzureStorageRepository]]):
118
+ """Query to list Azure Storage repositories."""
119
+
120
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None):
121
+ self.http_client = http_client
122
+ self.filter_params = filter_params
123
+
124
+ def execute(self) -> List[AzureStorageRepository]:
125
+ """Execute the list Azure Storage repositories query."""
126
+ params = {}
127
+ if self.filter_params:
128
+ params = self.filter_params.model_dump(exclude_none=True)
129
+
130
+ response = self.http_client.get("evidences/repositories/azure-storage", params=params)
131
+
132
+ if response.get("success"):
133
+ repositories_data = response.get("result", {}).get("entities", [])
134
+ return [AzureStorageRepository(**repo) for repo in repositories_data]
135
+
136
+ return []
137
+
138
+
139
+ class GetAzureStorageRepositoryQuery(Query[AzureStorageRepository]):
140
+ """Query to get Azure Storage repository by ID."""
141
+
142
+ def __init__(self, http_client: HTTPClient, repository_id: str):
143
+ self.http_client = http_client
144
+ self.repository_id = repository_id
145
+
146
+ def execute(self) -> AzureStorageRepository:
147
+ """Execute the get Azure Storage repository query."""
148
+ response = self.http_client.get(f"evidences/repositories/azure-storage/{self.repository_id}")
149
+
150
+ if response.get("success"):
151
+ repository_data = response.get("result", {})
152
+ return AzureStorageRepository(**repository_data)
153
+
154
+ raise Exception(f"Azure Storage repository not found: {self.repository_id}")
155
+
156
+
157
+ # FTPS Repository Queries
158
+
159
+ class ListFTPSRepositoriesQuery(Query[List[FTPSRepository]]):
160
+ """Query to list FTPS repositories."""
161
+
162
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None):
163
+ self.http_client = http_client
164
+ self.filter_params = filter_params
165
+
166
+ def execute(self) -> List[FTPSRepository]:
167
+ """Execute the list FTPS repositories query."""
168
+ params = {}
169
+ if self.filter_params:
170
+ params = self.filter_params.model_dump(exclude_none=True)
171
+
172
+ response = self.http_client.get("evidences/repositories/ftps", params=params)
173
+
174
+ if response.get("success"):
175
+ repositories_data = response.get("result", {}).get("entities", [])
176
+ return [FTPSRepository(**repo) for repo in repositories_data]
177
+
178
+ return []
179
+
180
+
181
+ class GetFTPSRepositoryQuery(Query[FTPSRepository]):
182
+ """Query to get FTPS repository by ID."""
183
+
184
+ def __init__(self, http_client: HTTPClient, repository_id: str):
185
+ self.http_client = http_client
186
+ self.repository_id = repository_id
187
+
188
+ def execute(self) -> FTPSRepository:
189
+ """Execute the get FTPS repository query."""
190
+ response = self.http_client.get(f"evidences/repositories/ftps/{self.repository_id}")
191
+
192
+ if response.get("success"):
193
+ repository_data = response.get("result", {})
194
+ return FTPSRepository(**repository_data)
195
+
196
+ raise Exception(f"FTPS repository not found: {self.repository_id}")
197
+
198
+
199
+ # SFTP Repository Queries
200
+
201
+ class ListSFTPRepositoriesQuery(Query[List[SFTPRepository]]):
202
+ """Query to list SFTP repositories."""
203
+
204
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None):
205
+ self.http_client = http_client
206
+ self.filter_params = filter_params
207
+
208
+ def execute(self) -> List[SFTPRepository]:
209
+ """Execute the list SFTP repositories query."""
210
+ params = {}
211
+ if self.filter_params:
212
+ params = self.filter_params.model_dump(exclude_none=True)
213
+
214
+ response = self.http_client.get("evidences/repositories/sftp", params=params)
215
+
216
+ if response.get("success"):
217
+ repositories_data = response.get("result", {}).get("entities", [])
218
+ return [SFTPRepository(**repo) for repo in repositories_data]
219
+
220
+ return []
221
+
222
+
223
+ class GetSFTPRepositoryQuery(Query[SFTPRepository]):
224
+ """Query to get SFTP repository by ID."""
225
+
226
+ def __init__(self, http_client: HTTPClient, repository_id: str):
227
+ self.http_client = http_client
228
+ self.repository_id = repository_id
229
+
230
+ def execute(self) -> SFTPRepository:
231
+ """Execute the get SFTP repository query."""
232
+ response = self.http_client.get(f"evidences/repositories/sftp/{self.repository_id}")
233
+
234
+ if response.get("success"):
235
+ repository_data = response.get("result", {})
236
+ return SFTPRepository(**repository_data)
237
+
238
+ raise Exception(f"SFTP repository not found: {self.repository_id}")
239
+
240
+
241
+ # SMB Repository Queries
242
+
243
+ class ListSMBRepositoriesQuery(Query[List[SMBRepository]]):
244
+ """Query to list SMB repositories."""
245
+
246
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[RepositoryFilter] = None):
247
+ self.http_client = http_client
248
+ self.filter_params = filter_params
249
+
250
+ def execute(self) -> List[SMBRepository]:
251
+ """Execute the list SMB repositories query."""
252
+ params = {}
253
+ if self.filter_params:
254
+ params = self.filter_params.model_dump(exclude_none=True)
255
+
256
+ response = self.http_client.get("evidences/repositories/smb", params=params)
257
+
258
+ if response.get("success"):
259
+ repositories_data = response.get("result", {}).get("entities", [])
260
+ return [SMBRepository(**repo) for repo in repositories_data]
261
+
262
+ return []
263
+
264
+
265
+ class GetSMBRepositoryQuery(Query[SMBRepository]):
266
+ """Query to get SMB repository by ID."""
267
+
268
+ def __init__(self, http_client: HTTPClient, repository_id: str):
269
+ self.http_client = http_client
270
+ self.repository_id = repository_id
271
+
272
+ def execute(self) -> SMBRepository:
273
+ """Execute the get SMB repository query."""
274
+ response = self.http_client.get(f"evidences/repositories/smb/{self.repository_id}")
275
+
276
+ if response.get("success"):
277
+ repository_data = response.get("result", {})
278
+ return SMBRepository(**repository_data)
279
+
280
+ raise Exception(f"SMB repository not found: {self.repository_id}")
@@ -0,0 +1,28 @@
1
+ """
2
+ Interact queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.interact import ShellInteraction
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class GetShellInteractionQuery(Query[ShellInteraction]):
13
+ """Query to get a specific shell interaction."""
14
+
15
+ def __init__(self, http_client: HTTPClient, interaction_id: str):
16
+ self.http_client = http_client
17
+ self.interaction_id = interaction_id
18
+
19
+ def execute(self) -> ShellInteraction:
20
+ """Execute the query to get a specific shell interaction."""
21
+ response = self.http_client.get(f"interact/shell/{self.interaction_id}")
22
+
23
+ if response.get("success"):
24
+ result_data = response.get("result", {})
25
+ # Use Pydantic parsing with proper field aliasing
26
+ return ShellInteraction.model_validate(result_data)
27
+
28
+ raise Exception(f"Shell interaction not found: {self.interaction_id}")
@@ -0,0 +1,223 @@
1
+ """
2
+ Organization-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.organizations import (
9
+ Organization, OrganizationUser, OrganizationRole, OrganizationLicense,
10
+ OrganizationSettings, OrganizationFilter, OrganizationsPaginatedResponse,
11
+ FilterOption, OrganizationUsersPaginatedResponse, CheckOrganizationNameExistsResponse,
12
+ ShareableDeploymentInfoResponse
13
+ )
14
+ from ..http_client import HTTPClient
15
+
16
+
17
+ class ListOrganizationsQuery(Query[OrganizationsPaginatedResponse]):
18
+ """Query to list organizations with optional filtering."""
19
+
20
+ def __init__(self, http_client: HTTPClient, page: int = 1, page_size: int = 10,
21
+ sort_by: str = "name", order: str = "asc", filter_params: Optional[OrganizationFilter] = None):
22
+ self.http_client = http_client
23
+ self.page = page
24
+ self.page_size = page_size
25
+ self.sort_by = sort_by
26
+ self.order = order
27
+ self.filter_params = filter_params or OrganizationFilter()
28
+
29
+ def execute(self) -> OrganizationsPaginatedResponse:
30
+ """Execute the query to list organizations with complete parameter support."""
31
+ # Build query parameters
32
+ params = {
33
+ "pageNumber": self.page,
34
+ "pageSize": self.page_size,
35
+ "sortBy": self.sort_by,
36
+ "sortType": self.order.upper(), # ASC or DESC
37
+ }
38
+
39
+ # Add filter parameters using proper API structure
40
+ filter_params = self.filter_params.to_params()
41
+ params.update(filter_params)
42
+
43
+ response = self.http_client.get("organizations", params=params)
44
+
45
+ result = response.get("result", {})
46
+
47
+ # Use Pydantic model_validate for automatic field mapping
48
+ return OrganizationsPaginatedResponse.model_validate(result)
49
+
50
+
51
+ class GetOrganizationQuery(Query[Organization]):
52
+ """Query to get a specific organization by ID."""
53
+
54
+ def __init__(self, http_client: HTTPClient, organization_id: str):
55
+ self.http_client = http_client
56
+ self.organization_id = organization_id
57
+
58
+ def execute(self) -> Organization:
59
+ """Execute the query to get organization details."""
60
+ response = self.http_client.get(f"organizations/{self.organization_id}")
61
+
62
+ entity_data = response.get("result", {})
63
+
64
+ # Use Pydantic model_validate for automatic field mapping via aliases
65
+ return Organization.model_validate(entity_data)
66
+
67
+
68
+ class GetOrganizationUsersQuery(Query[OrganizationUsersPaginatedResponse]):
69
+ """Query to get users for a specific organization with complete field mapping."""
70
+
71
+ def __init__(self, http_client: HTTPClient, organization_id: str, page: int = 1, page_size: int = 10):
72
+ self.http_client = http_client
73
+ self.organization_id = organization_id
74
+ self.page = page
75
+ self.page_size = page_size
76
+
77
+ def execute(self) -> OrganizationUsersPaginatedResponse:
78
+ """Execute the query to get organization users with complete field mapping."""
79
+ # Add pagination parameters
80
+ params = {
81
+ "pageNumber": self.page,
82
+ "pageSize": self.page_size
83
+ }
84
+
85
+ response = self.http_client.get(f"organizations/{self.organization_id}/users", params=params)
86
+
87
+ result = response.get("result", {})
88
+
89
+ # Use Pydantic model_validate for automatic field mapping
90
+ return OrganizationUsersPaginatedResponse.model_validate(result)
91
+
92
+
93
+ class CheckOrganizationNameExistsQuery(Query[CheckOrganizationNameExistsResponse]):
94
+ """Query to check if an organization name exists."""
95
+
96
+ def __init__(self, http_client: HTTPClient, name: str):
97
+ self.http_client = http_client
98
+ self.name = name
99
+
100
+ def execute(self) -> CheckOrganizationNameExistsResponse:
101
+ """Execute the query to check organization name availability."""
102
+ params = {"name": self.name}
103
+
104
+ response = self.http_client.get("organizations/check", params=params)
105
+
106
+ # Use Pydantic model_validate for automatic field mapping
107
+ return CheckOrganizationNameExistsResponse.model_validate(response)
108
+
109
+
110
+ class GetShareableDeploymentInfoQuery(Query[ShareableDeploymentInfoResponse]):
111
+ """Query to get shareable deployment information by token."""
112
+
113
+ def __init__(self, http_client: HTTPClient, token: str):
114
+ self.http_client = http_client
115
+ self.token = token
116
+
117
+ def execute(self) -> ShareableDeploymentInfoResponse:
118
+ """Execute the query to get shareable deployment information."""
119
+ params = {"token": self.token}
120
+
121
+ response = self.http_client.get("organizations/shareable-deployment", params=params)
122
+
123
+ # Use Pydantic model_validate for automatic field mapping
124
+ return ShareableDeploymentInfoResponse.model_validate(response)
125
+
126
+
127
+ class GetOrganizationRolesQuery(Query[List[OrganizationRole]]):
128
+ """Query to get roles for a specific organization."""
129
+
130
+ def __init__(self, http_client: HTTPClient, organization_id: str):
131
+ self.http_client = http_client
132
+ self.organization_id = organization_id
133
+
134
+ def execute(self) -> List[OrganizationRole]:
135
+ """Execute the query to get organization roles."""
136
+ response = self.http_client.get(f"organizations/{self.organization_id}/roles")
137
+
138
+ entities = response.get("result", {}).get("entities", [])
139
+
140
+ roles = []
141
+ for entity_data in entities:
142
+ mapped_data = {
143
+ "id": entity_data.get("_id"),
144
+ "name": entity_data.get("name"),
145
+ "description": entity_data.get("description"),
146
+ "organization_id": entity_data.get("organizationId"),
147
+ "permissions": entity_data.get("permissions", []),
148
+ "created_at": entity_data.get("createdAt"),
149
+ "updated_at": entity_data.get("updatedAt"),
150
+ "created_by": entity_data.get("createdBy"),
151
+ "is_system": entity_data.get("isSystem", False),
152
+ "user_count": entity_data.get("userCount", 0),
153
+ }
154
+
155
+ # Remove None values
156
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
157
+
158
+ roles.append(OrganizationRole(**mapped_data))
159
+
160
+ return roles
161
+
162
+
163
+ class GetOrganizationLicensesQuery(Query[List[OrganizationLicense]]):
164
+ """Query to get licenses for a specific organization."""
165
+
166
+ def __init__(self, http_client: HTTPClient, organization_id: str):
167
+ self.http_client = http_client
168
+ self.organization_id = organization_id
169
+
170
+ def execute(self) -> List[OrganizationLicense]:
171
+ """Execute the query to get organization licenses."""
172
+ response = self.http_client.get(f"organizations/{self.organization_id}/licenses")
173
+
174
+ entities = response.get("result", {}).get("entities", [])
175
+
176
+ licenses = []
177
+ for entity_data in entities:
178
+ mapped_data = {
179
+ "id": entity_data.get("_id"),
180
+ "organization_id": entity_data.get("organizationId"),
181
+ "license_type": entity_data.get("licenseType"),
182
+ "total_licenses": entity_data.get("totalLicenses", 0),
183
+ "used_licenses": entity_data.get("usedLicenses", 0),
184
+ "valid_from": entity_data.get("validFrom"),
185
+ "valid_until": entity_data.get("validUntil"),
186
+ "features": entity_data.get("features", []),
187
+ "is_active": entity_data.get("isActive", True),
188
+ }
189
+
190
+ # Remove None values
191
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
192
+
193
+ licenses.append(OrganizationLicense(**mapped_data))
194
+
195
+ return licenses
196
+
197
+
198
+ class GetOrganizationSettingsQuery(Query[OrganizationSettings]):
199
+ """Query to get settings for a specific organization."""
200
+
201
+ def __init__(self, http_client: HTTPClient, organization_id: str):
202
+ self.http_client = http_client
203
+ self.organization_id = organization_id
204
+
205
+ def execute(self) -> OrganizationSettings:
206
+ """Execute the query to get organization settings."""
207
+ response = self.http_client.get(f"organizations/{self.organization_id}/settings")
208
+
209
+ entity_data = response.get("result", {})
210
+
211
+ mapped_data = {
212
+ "organization_id": entity_data.get("organizationId"),
213
+ "retention_policy": entity_data.get("retentionPolicy", {}),
214
+ "security_settings": entity_data.get("securitySettings", {}),
215
+ "notification_settings": entity_data.get("notificationSettings", {}),
216
+ "api_settings": entity_data.get("apiSettings", {}),
217
+ "custom_settings": entity_data.get("customSettings", {}),
218
+ }
219
+
220
+ # Remove None values
221
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
222
+
223
+ return OrganizationSettings(**mapped_data)