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,201 @@
1
+ """Multipart Upload API client for Binalyze AIR SDK."""
2
+
3
+ from typing import Optional, Dict, Any, Callable
4
+ from ..http_client import HTTPClient
5
+ from ..models.multipart_upload import (
6
+ UploadInitializeResponse, UploadPartResponse, UploadStatusResponse,
7
+ UploadFinalizeResponse, MultipartUploadSession
8
+ )
9
+ from ..queries.multipart_upload import (
10
+ InitializeUploadQuery, UploadPartQuery, CheckUploadStatusQuery, FinalizeUploadQuery
11
+ )
12
+ from ..commands.multipart_upload import (
13
+ InitializeUploadCommand, UploadPartCommand, CheckUploadStatusCommand,
14
+ FinalizeUploadCommand, CompleteFileUploadCommand, AbortUploadCommand
15
+ )
16
+
17
+
18
+ class MultipartUploadAPI:
19
+ """Multipart Upload API with CQRS pattern - separated queries and commands.
20
+
21
+ This class provides methods for uploading large files using multipart upload,
22
+ which allows for resumable uploads and better handling of large files.
23
+ """
24
+
25
+ def __init__(self, http_client: HTTPClient):
26
+ """Initialize Multipart Upload API client.
27
+
28
+ Args:
29
+ http_client: HTTP client for making API requests
30
+ """
31
+ self.http_client = http_client
32
+
33
+ # QUERIES (Read operations)
34
+ def check_upload_status(self, file_id: str) -> UploadStatusResponse:
35
+ """Check if upload is ready to be finalized.
36
+
37
+ Args:
38
+ file_id: Upload session identifier
39
+
40
+ Returns:
41
+ UploadStatusResponse with ready status
42
+
43
+ Raises:
44
+ APIError: If the request fails
45
+ """
46
+ command = CheckUploadStatusCommand(self, file_id)
47
+ return command.execute()
48
+
49
+ # COMMANDS (Write operations)
50
+ def initialize_upload(self) -> UploadInitializeResponse:
51
+ """Initialize a new multipart upload session.
52
+
53
+ Returns:
54
+ UploadInitializeResponse with file ID for the session
55
+
56
+ Raises:
57
+ APIError: If the request fails
58
+ """
59
+ command = InitializeUploadCommand(self)
60
+ return command.execute()
61
+
62
+ def upload_part(self, file_id: str, part_number: int, file_data: bytes, filename: Optional[str] = None) -> UploadPartResponse:
63
+ """Upload a file part in a multipart upload.
64
+
65
+ Args:
66
+ file_id: Upload session identifier
67
+ part_number: Sequential part number (starting from 1)
68
+ file_data: Binary file data for this part
69
+ filename: Optional filename for the part
70
+
71
+ Returns:
72
+ UploadPartResponse indicating success/failure
73
+
74
+ Raises:
75
+ APIError: If the request fails
76
+ """
77
+ command = UploadPartCommand(self, file_id, part_number, file_data, filename)
78
+ return command.execute()
79
+
80
+ def finalize_upload(self, file_id: str) -> UploadFinalizeResponse:
81
+ """Finalize a multipart upload session.
82
+
83
+ Args:
84
+ file_id: Upload session identifier to finalize
85
+
86
+ Returns:
87
+ UploadFinalizeResponse indicating success/failure
88
+
89
+ Raises:
90
+ APIError: If the request fails
91
+ """
92
+ command = FinalizeUploadCommand(self, file_id)
93
+ return command.execute()
94
+
95
+ def abort_upload(self, file_id: str) -> Dict[str, Any]:
96
+ """Abort a multipart upload session and discard all uploaded parts.
97
+
98
+ Args:
99
+ file_id: Upload session identifier to abort
100
+
101
+ Returns:
102
+ Dictionary with API response
103
+ """
104
+ command = AbortUploadCommand(self, file_id)
105
+ return command.execute()
106
+
107
+ def upload_file(
108
+ self,
109
+ file_path: str,
110
+ chunk_size: int = 5 * 1024 * 1024,
111
+ progress_callback: Optional[Callable[[float, int, int], None]] = None
112
+ ) -> Dict[str, Any]:
113
+ """Upload a complete file using multipart upload.
114
+
115
+ This method handles the entire upload process automatically:
116
+ 1. Initialize upload session
117
+ 2. Upload all file parts
118
+ 3. Check upload status
119
+ 4. Finalize upload
120
+
121
+ Args:
122
+ file_path: Path to the file to upload
123
+ chunk_size: Size of each chunk in bytes (default 5MB)
124
+ progress_callback: Optional callback for progress updates (percentage, current_part, total_parts)
125
+
126
+ Returns:
127
+ Dictionary with upload results and session information
128
+
129
+ Raises:
130
+ APIError: If any step of the upload fails
131
+ FileNotFoundError: If the file doesn't exist
132
+ """
133
+ command = CompleteFileUploadCommand(self, file_path, chunk_size, progress_callback)
134
+ return command.execute()
135
+
136
+ # Low-level query methods (for internal use by commands)
137
+ def _initialize_upload_query(self, query: InitializeUploadQuery) -> Dict[str, Any]:
138
+ """Execute initialize upload query (internal use).
139
+
140
+ Args:
141
+ query: InitializeUploadQuery instance
142
+
143
+ Returns:
144
+ API response dictionary
145
+ """
146
+ body = query.build_body()
147
+ return self.http_client.post('multipart-upload/initialize', json_data=body)
148
+
149
+ def _upload_part_query(self, query: UploadPartQuery) -> Dict[str, Any]:
150
+ """Execute upload part query (internal use).
151
+
152
+ Args:
153
+ query: UploadPartQuery instance
154
+
155
+ Returns:
156
+ API response dictionary
157
+ """
158
+ form_data = query.build_form_data()
159
+
160
+ # Prepare files and data for multipart upload
161
+ # Use the filename from the request, or default to 'part.bin'
162
+ filename = getattr(query._request, 'filename', None) or 'part.bin'
163
+ files = {
164
+ 'file': (filename, form_data['file'], 'application/octet-stream')
165
+ }
166
+
167
+ data = {
168
+ 'fileId': form_data['fileId'],
169
+ 'partNumber': form_data['partNumber']
170
+ }
171
+
172
+ return self.http_client.upload_multipart(
173
+ 'multipart-upload/upload-part',
174
+ files=files,
175
+ data=data,
176
+ method='PUT'
177
+ )
178
+
179
+ def _check_upload_status_query(self, query: CheckUploadStatusQuery) -> Dict[str, Any]:
180
+ """Execute check upload status query (internal use).
181
+
182
+ Args:
183
+ query: CheckUploadStatusQuery instance
184
+
185
+ Returns:
186
+ API response dictionary
187
+ """
188
+ params = query.build_params()
189
+ return self.http_client.get('multipart-upload/is-ready', params=params)
190
+
191
+ def _finalize_upload_query(self, query: FinalizeUploadQuery) -> Dict[str, Any]:
192
+ """Execute finalize upload query (internal use).
193
+
194
+ Args:
195
+ query: FinalizeUploadQuery instance
196
+
197
+ Returns:
198
+ API response dictionary
199
+ """
200
+ body = query.build_body()
201
+ return self.http_client.post('multipart-upload/finalize', json_data=body)
@@ -0,0 +1,115 @@
1
+ """
2
+ Notifications API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import Optional, Union, Dict, Any
6
+
7
+ from ..http_client import HTTPClient
8
+ from ..models.notifications import Notification, NotificationsList, NotificationsFilter, NotificationType, NotificationLevel
9
+ from ..queries.notifications import GetNotificationsQuery, GetNotificationByIdQuery
10
+ from ..commands.notifications import DeleteAllNotificationsCommand, MarkAllAsReadCommand, MarkAsReadByIdCommand
11
+
12
+
13
+ class NotificationsAPI:
14
+ """Notifications API with CQRS pattern - separated queries and commands."""
15
+
16
+ def __init__(self, http_client: HTTPClient):
17
+ self.http_client = http_client
18
+
19
+ # QUERIES (Read operations)
20
+ def get_notifications(self, filter_params: Optional[NotificationsFilter] = None) -> NotificationsList:
21
+ """Get notifications with optional filtering."""
22
+ query = GetNotificationsQuery(self.http_client, filter_params)
23
+ return query.execute()
24
+
25
+ def get_notification_by_id(self, notification_id: Union[int, str]) -> Optional[Notification]:
26
+ """Get a specific notification by ID."""
27
+ query = GetNotificationByIdQuery(self.http_client, str(notification_id))
28
+ return query.execute()
29
+
30
+ # Convenience methods for common queries
31
+ def get_unread_notifications(self, organization_ids: Union[int, list] = 0) -> NotificationsList:
32
+ """Get unread notifications."""
33
+ # Create filter with explicit field values
34
+ filter_params = NotificationsFilter()
35
+ filter_params.is_read = False
36
+ if isinstance(organization_ids, list):
37
+ filter_params.organization_ids = organization_ids
38
+ else:
39
+ filter_params.organization_ids = organization_ids
40
+
41
+ return self.get_notifications(filter_params)
42
+
43
+ def get_notifications_by_type(self, notification_type: Union[str, NotificationType], organization_ids: Union[int, list] = 0) -> NotificationsList:
44
+ """Get notifications by type."""
45
+ # Create filter with explicit field values
46
+ filter_params = NotificationsFilter()
47
+ if isinstance(notification_type, str):
48
+ filter_params.type = NotificationType(notification_type)
49
+ else:
50
+ filter_params.type = notification_type
51
+ if isinstance(organization_ids, list):
52
+ filter_params.organization_ids = organization_ids
53
+ else:
54
+ filter_params.organization_ids = organization_ids
55
+
56
+ return self.get_notifications(filter_params)
57
+
58
+ def get_notifications_by_level(self, level: Union[str, NotificationLevel], organization_ids: Union[int, list] = 0) -> NotificationsList:
59
+ """Get notifications by level."""
60
+ # Create filter with explicit field values
61
+ filter_params = NotificationsFilter()
62
+ if isinstance(level, str):
63
+ filter_params.level = NotificationLevel(level)
64
+ else:
65
+ filter_params.level = level
66
+ if isinstance(organization_ids, list):
67
+ filter_params.organization_ids = organization_ids
68
+ else:
69
+ filter_params.organization_ids = organization_ids
70
+
71
+ return self.get_notifications(filter_params)
72
+
73
+ # COMMANDS (Write operations)
74
+ def delete_all_notifications(self) -> Dict[str, Any]:
75
+ """Delete all notifications for the current user."""
76
+ command = DeleteAllNotificationsCommand(self.http_client)
77
+ return command.execute()
78
+
79
+ def mark_all_as_read(self) -> Dict[str, Any]:
80
+ """Mark all notifications as read."""
81
+ command = MarkAllAsReadCommand(self.http_client)
82
+ return command.execute()
83
+
84
+ def mark_as_read_by_id(self, notification_id: Union[int, str]) -> Dict[str, Any]:
85
+ """Mark a specific notification as read by ID."""
86
+ command = MarkAsReadByIdCommand(self.http_client, notification_id)
87
+ return command.execute()
88
+
89
+ # Convenience methods
90
+ def get_notification_count(self, organization_ids: Union[int, list] = 0) -> int:
91
+ """Get total notification count."""
92
+ # Create filter with explicit field values
93
+ filter_params = NotificationsFilter()
94
+ if isinstance(organization_ids, list):
95
+ filter_params.organization_ids = organization_ids
96
+ else:
97
+ filter_params.organization_ids = organization_ids
98
+ filter_params.page_size = 1 # We only need the count
99
+
100
+ result = self.get_notifications(filter_params)
101
+ return result.total_entity_count
102
+
103
+ def get_unread_count(self, organization_ids: Union[int, list] = 0) -> int:
104
+ """Get unread notification count."""
105
+ # Create filter with explicit field values
106
+ filter_params = NotificationsFilter()
107
+ filter_params.is_read = False
108
+ if isinstance(organization_ids, list):
109
+ filter_params.organization_ids = organization_ids
110
+ else:
111
+ filter_params.organization_ids = organization_ids
112
+ filter_params.page_size = 1 # We only need the count
113
+
114
+ result = self.get_notifications(filter_params)
115
+ return result.total_entity_count
@@ -0,0 +1,267 @@
1
+ """
2
+ Organizations API for the Binalyze AIR SDK using CQRS pattern.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+ from ..http_client import HTTPClient
7
+ from ..models.organizations import (
8
+ Organization, OrganizationsPaginatedResponse, OrganizationUsersPaginatedResponse,
9
+ OrganizationUser, OrganizationSettings, CreateOrganizationRequest, UpdateOrganizationRequest,
10
+ AddUserToOrganizationRequest, AssignUsersToOrganizationRequest
11
+ )
12
+ from ..queries.organizations import (
13
+ ListOrganizationsQuery,
14
+ GetOrganizationQuery,
15
+ GetOrganizationUsersQuery,
16
+ )
17
+ from ..commands.organizations import (
18
+ CreateOrganizationCommand,
19
+ UpdateOrganizationCommand,
20
+ AssignUsersToOrganizationCommand,
21
+ RemoveUserFromOrganizationCommand,
22
+ UpdateOrganizationSettingsCommand,
23
+ )
24
+
25
+
26
+ class OrganizationsAPI:
27
+ """Organizations API with CQRS pattern - separated queries and commands."""
28
+
29
+ def __init__(self, http_client: HTTPClient):
30
+ self.http_client = http_client
31
+
32
+ # QUERIES (Read operations)
33
+ def list(self, page: int = 1, page_size: int = 10,
34
+ sort_by: str = "name", order: str = "asc") -> OrganizationsPaginatedResponse:
35
+ """List organizations with pagination and sorting."""
36
+ query = ListOrganizationsQuery(self.http_client, page, page_size, sort_by, order, None)
37
+ return query.execute()
38
+
39
+ def get(self, organization_id: str) -> Organization:
40
+ """Get organization by ID."""
41
+ query = GetOrganizationQuery(self.http_client, organization_id)
42
+ return query.execute()
43
+
44
+ def get_users(self, organization_id: str, page: int = 1, page_size: int = 10) -> OrganizationUsersPaginatedResponse:
45
+ """Get users in organization."""
46
+ query = GetOrganizationUsersQuery(self.http_client, organization_id, page, page_size)
47
+ return query.execute()
48
+
49
+ def check_name(self, name: str) -> bool:
50
+ """Check if organization name exists."""
51
+ try:
52
+ params = {"name": name}
53
+ response = self.http_client.get("organizations/check", params=params)
54
+ return response.get("result", False)
55
+ except Exception:
56
+ return False
57
+
58
+ def get_shareable_deployment_info(self, deployment_token: str) -> Dict[str, Any]:
59
+ """Get shareable deployment information by token."""
60
+ try:
61
+ response = self.http_client.get(f"organizations/shareable-deployment-info/{deployment_token}")
62
+
63
+ if response.get("success"):
64
+ return response.get("result", {})
65
+ else:
66
+ # Return error information
67
+ return {
68
+ "error": True,
69
+ "errors": response.get("errors", []),
70
+ "statusCode": response.get("statusCode", 500)
71
+ }
72
+ except Exception as e:
73
+ return {
74
+ "error": True,
75
+ "errors": [str(e)],
76
+ "statusCode": 500
77
+ }
78
+
79
+ # COMMANDS (Write operations)
80
+ def create(self, request: CreateOrganizationRequest) -> Organization:
81
+ """Create organization."""
82
+ command = CreateOrganizationCommand(self.http_client, request)
83
+ return command.execute()
84
+
85
+ def update(self, organization_id: str, request: UpdateOrganizationRequest) -> Organization:
86
+ """Update organization."""
87
+ command = UpdateOrganizationCommand(self.http_client, organization_id, request)
88
+ return command.execute()
89
+
90
+ def add_user(self, organization_id: str, user_ids: List[str]) -> bool:
91
+ """Add users to organization using the modern assign users endpoint."""
92
+ return self.assign_users(organization_id, user_ids)
93
+
94
+ def assign_users(self, organization_id: str, user_ids: List[str]) -> bool:
95
+ """Assign users to organization using the /assign-users endpoint."""
96
+ # Create the proper request object with correct field name
97
+ request = AssignUsersToOrganizationRequest(userIds=user_ids)
98
+ command = AssignUsersToOrganizationCommand(self.http_client, organization_id, request)
99
+ return command.execute()
100
+
101
+ def remove_user(self, organization_id: str, user_id: str) -> Dict[str, Any]:
102
+ """Remove user from organization using the /remove-user endpoint."""
103
+ command = RemoveUserFromOrganizationCommand(self.http_client, organization_id, user_id)
104
+ return command.execute()
105
+
106
+ def update_settings(self, organization_id: str, settings: Dict[str, Any]) -> OrganizationSettings:
107
+ """Update organization settings."""
108
+ command = UpdateOrganizationSettingsCommand(self.http_client, organization_id, settings)
109
+ return command.execute()
110
+
111
+ def update_shareable_deployment_settings(self, organization_id: int, status: bool) -> Dict[str, Any]:
112
+ """Update organization shareable deployment settings."""
113
+ try:
114
+ # Prepare the payload according to API specification
115
+ payload = {"status": status}
116
+
117
+ # Make the API call
118
+ response = self.http_client.post(f"organizations/{organization_id}/shareable-deployment", json_data=payload)
119
+ return response
120
+
121
+ except Exception as e:
122
+ # Check if it's a 409 conflict (expected behavior when setting to same state)
123
+ error_msg = str(e)
124
+ if "409" in error_msg or "already" in error_msg.lower():
125
+ # Return success for 409 conflicts (expected behavior)
126
+ return {
127
+ "success": True,
128
+ "result": None,
129
+ "statusCode": 409,
130
+ "message": "Shareable deployment setting already in desired state"
131
+ }
132
+
133
+ # Return error response format matching API
134
+ return {
135
+ "success": False,
136
+ "result": None,
137
+ "statusCode": 500,
138
+ "errors": [str(e)]
139
+ }
140
+
141
+ def update_deployment_token(self, organization_id: int, deployment_token: str) -> Dict[str, Any]:
142
+ """Update organization deployment token."""
143
+ try:
144
+ # Prepare the payload according to API specification
145
+ payload = {"deploymentToken": deployment_token}
146
+
147
+ # Make the API call
148
+ response = self.http_client.post(f"organizations/{organization_id}/deployment-token", json_data=payload)
149
+ return response
150
+
151
+ except Exception as e:
152
+ # Check if it's a 409 conflict (expected behavior when setting to same token)
153
+ error_msg = str(e)
154
+ if "409" in error_msg or "same token" in error_msg.lower() or "cannot be updated with same" in error_msg.lower():
155
+ # Return success for 409 conflicts (expected behavior)
156
+ return {
157
+ "success": True,
158
+ "result": None,
159
+ "statusCode": 409,
160
+ "message": "Deployment token already set to this value"
161
+ }
162
+
163
+ # Return error response format matching API
164
+ return {
165
+ "success": False,
166
+ "result": None,
167
+ "statusCode": 500,
168
+ "errors": [str(e)]
169
+ }
170
+
171
+ def delete(self, organization_id: int) -> Dict[str, Any]:
172
+ """Delete organization by ID."""
173
+ try:
174
+ # Make the API call
175
+ response = self.http_client.delete(f"organizations/{organization_id}")
176
+ return response
177
+
178
+ except Exception as e:
179
+ # Return error response format matching API
180
+ return {
181
+ "success": False,
182
+ "result": None,
183
+ "statusCode": 500,
184
+ "errors": [str(e)]
185
+ }
186
+
187
+ def add_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
188
+ """Add tags to organization."""
189
+ try:
190
+ # Prepare the payload according to API specification
191
+ payload = {"tags": tags}
192
+
193
+ # Make the API call using PATCH method
194
+ response = self.http_client.patch(f"organizations/{organization_id}/tags", json_data=payload)
195
+ return response
196
+
197
+ except Exception as e:
198
+ # Return error response format matching API
199
+ return {
200
+ "success": False,
201
+ "result": None,
202
+ "statusCode": 500,
203
+ "errors": [str(e)]
204
+ }
205
+
206
+ def delete_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
207
+ """Delete tags from organization."""
208
+ try:
209
+ # Prepare the payload according to API specification
210
+ payload = {"tags": tags}
211
+
212
+ # Make the API call using DELETE method
213
+ response = self.http_client.delete(f"organizations/{organization_id}/tags", json_data=payload)
214
+ return response
215
+
216
+ except Exception as e:
217
+ # Return error response format matching API
218
+ return {
219
+ "success": False,
220
+ "result": None,
221
+ "statusCode": 500,
222
+ "errors": [str(e)]
223
+ }
224
+
225
+ def remove_tags(self, organization_id: int, tags: List[str]) -> Dict[str, Any]:
226
+ """Remove tags from organization (alias for delete_tags)."""
227
+ return self.delete_tags(organization_id, tags)
228
+
229
+ # ------------------------------------------------------------------
230
+ # New endpoints: user invitation & activation toggles
231
+ # ------------------------------------------------------------------
232
+
233
+ def resend_invitation(self, organization_id: int, user_id: str) -> Dict[str, Any]:
234
+ """Resend organization invitation email to a user.
235
+
236
+ Mirrors POST /organizations/{orgId}/resend-invitation/{userId}
237
+ """
238
+ try:
239
+ return self.http_client.post(
240
+ f"organizations/{organization_id}/resend-invitation/{user_id}",
241
+ json_data={},
242
+ )
243
+ except Exception as e:
244
+ return {
245
+ "success": False,
246
+ "result": None,
247
+ "statusCode": 500,
248
+ "errors": [str(e)],
249
+ }
250
+
251
+ def toggle_user_activation(self, organization_id: int, user_id: str) -> Dict[str, Any]:
252
+ """Toggle active / inactive status for a user in the organization.
253
+
254
+ Endpoint: POST /organizations/{orgId}/toggle-user-activation/{userId}
255
+ """
256
+ try:
257
+ return self.http_client.post(
258
+ f"organizations/{organization_id}/toggle-user-activation/{user_id}",
259
+ json_data={},
260
+ )
261
+ except Exception as e:
262
+ return {
263
+ "success": False,
264
+ "result": None,
265
+ "statusCode": 500,
266
+ "errors": [str(e)],
267
+ }
@@ -1,40 +1,45 @@
1
- """
2
- Params API for the Binalyze AIR SDK.
3
- """
4
-
5
- from typing import List
6
-
7
- from ..http_client import HTTPClient
8
- from ..models.params import AcquisitionArtifact, EDiscoveryPattern, AcquisitionEvidence, DroneAnalyzer
9
- from ..queries.params import (
10
- GetAcquisitionArtifactsQuery, GetEDiscoveryPatternsQuery,
11
- GetAcquisitionEvidencesQuery, GetDroneAnalyzersQuery
12
- )
13
-
14
-
15
- class ParamsAPI:
16
- """Params API with CQRS pattern - read-only operations for parameters."""
17
-
18
- def __init__(self, http_client: HTTPClient):
19
- self.http_client = http_client
20
-
21
- # QUERIES (Read operations only - params are read-only)
22
- def get_acquisition_artifacts(self) -> List[AcquisitionArtifact]:
23
- """Get available acquisition artifacts."""
24
- query = GetAcquisitionArtifactsQuery(self.http_client)
25
- return query.execute()
26
-
27
- def get_ediscovery_patterns(self) -> List[EDiscoveryPattern]:
28
- """Get available e-discovery patterns."""
29
- query = GetEDiscoveryPatternsQuery(self.http_client)
30
- return query.execute()
31
-
32
- def get_acquisition_evidences(self) -> List[AcquisitionEvidence]:
33
- """Get available acquisition evidence types."""
34
- query = GetAcquisitionEvidencesQuery(self.http_client)
35
- return query.execute()
36
-
37
- def get_drone_analyzers(self) -> List[DroneAnalyzer]:
38
- """Get available drone analyzers."""
39
- query = GetDroneAnalyzersQuery(self.http_client)
1
+ """
2
+ Params API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List
6
+
7
+ from ..http_client import HTTPClient
8
+ from ..models.params import AcquisitionArtifact, EDiscoveryPattern, AcquisitionEvidence, DroneAnalyzer, MitreAttackTactic
9
+ from ..queries.params import (
10
+ GetAcquisitionArtifactsQuery, GetEDiscoveryPatternsQuery,
11
+ GetAcquisitionEvidencesQuery, GetDroneAnalyzersQuery, GetMitreAttackTacticsQuery
12
+ )
13
+
14
+
15
+ class ParamsAPI:
16
+ """Params API with CQRS pattern - read-only operations for parameters."""
17
+
18
+ def __init__(self, http_client: HTTPClient):
19
+ self.http_client = http_client
20
+
21
+ # QUERIES (Read operations only - params are read-only)
22
+ def get_acquisition_artifacts(self) -> List[AcquisitionArtifact]:
23
+ """Get available acquisition artifacts."""
24
+ query = GetAcquisitionArtifactsQuery(self.http_client)
25
+ return query.execute()
26
+
27
+ def get_ediscovery_patterns(self) -> List[EDiscoveryPattern]:
28
+ """Get available e-discovery patterns."""
29
+ query = GetEDiscoveryPatternsQuery(self.http_client)
30
+ return query.execute()
31
+
32
+ def get_acquisition_evidences(self) -> List[AcquisitionEvidence]:
33
+ """Get available acquisition evidence types."""
34
+ query = GetAcquisitionEvidencesQuery(self.http_client)
35
+ return query.execute()
36
+
37
+ def get_drone_analyzers(self) -> List[DroneAnalyzer]:
38
+ """Get available drone analyzers."""
39
+ query = GetDroneAnalyzersQuery(self.http_client)
40
+ return query.execute()
41
+
42
+ def get_mitre_attack_tactics(self) -> List[MitreAttackTactic]:
43
+ """Get available MITRE ATT&CK tactics."""
44
+ query = GetMitreAttackTacticsQuery(self.http_client)
40
45
  return query.execute()