binalyze-air-sdk 1.0.2__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.2.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.2.dist-info/METADATA +0 -706
  141. binalyze_air_sdk-1.0.2.dist-info/RECORD +0 -82
  142. {binalyze_air_sdk-1.0.2.dist-info → binalyze_air_sdk-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,75 +1,79 @@
1
- """
2
- Auto Asset Tags API for the Binalyze AIR SDK.
3
- """
4
-
5
- from typing import List, Optional, Dict, Any, Union
6
-
7
- from ..http_client import HTTPClient
8
- from ..models.auto_asset_tags import (
9
- AutoAssetTag, AutoAssetTagFilter, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
10
- StartTaggingRequest, TaggingResponse
11
- )
12
- from ..queries.auto_asset_tags import ListAutoAssetTagsQuery, GetAutoAssetTagQuery
13
- from ..commands.auto_asset_tags import (
14
- CreateAutoAssetTagCommand, UpdateAutoAssetTagCommand, DeleteAutoAssetTagCommand,
15
- StartTaggingCommand
16
- )
17
-
18
-
19
- class AutoAssetTagsAPI:
20
- """Auto Asset Tags API with CQRS pattern - separated queries and commands."""
21
-
22
- def __init__(self, http_client: HTTPClient):
23
- self.http_client = http_client
24
-
25
- # QUERIES (Read operations)
26
- def list(self, filter_params: Optional[AutoAssetTagFilter] = None) -> List[AutoAssetTag]:
27
- """List auto asset tags with optional filtering."""
28
- query = ListAutoAssetTagsQuery(self.http_client, filter_params)
29
- return query.execute()
30
-
31
- def get(self, tag_id: str) -> AutoAssetTag:
32
- """Get a specific auto asset tag by ID."""
33
- query = GetAutoAssetTagQuery(self.http_client, tag_id)
34
- return query.execute()
35
-
36
- def get_by_id(self, tag_id: str) -> AutoAssetTag:
37
- """Get a specific auto asset tag by ID - alias for get."""
38
- return self.get(tag_id)
39
-
40
- # COMMANDS (Write operations)
41
- def create(self, request: Union[CreateAutoAssetTagRequest, Dict[str, Any]]) -> AutoAssetTag:
42
- """Create a new auto asset tag."""
43
- command = CreateAutoAssetTagCommand(self.http_client, request)
44
- return command.execute()
45
-
46
- def update(self, tag_id_or_data: Union[str, Dict[str, Any]], request: Optional[Union[UpdateAutoAssetTagRequest, Dict[str, Any]]] = None) -> AutoAssetTag:
47
- """Update an existing auto asset tag."""
48
- # Handle both signatures: update(tag_id, request) and update(data_dict)
49
- if isinstance(tag_id_or_data, str) and request is not None:
50
- # Traditional signature: update(tag_id, request)
51
- command = UpdateAutoAssetTagCommand(self.http_client, tag_id_or_data, request)
52
- elif isinstance(tag_id_or_data, dict):
53
- # Dict signature: update(data_dict) where data_dict contains 'id'
54
- tag_id = tag_id_or_data.get('id')
55
- if not tag_id:
56
- raise ValueError("Tag ID must be provided in data dict or as separate parameter")
57
- command = UpdateAutoAssetTagCommand(self.http_client, tag_id, tag_id_or_data)
58
- else:
59
- raise ValueError("Invalid arguments for update")
60
-
61
- return command.execute()
62
-
63
- def delete(self, tag_id: str) -> Dict[str, Any]:
64
- """Delete an auto asset tag."""
65
- command = DeleteAutoAssetTagCommand(self.http_client, tag_id)
66
- return command.execute()
67
-
68
- def delete_by_id(self, tag_id: str) -> Dict[str, Any]:
69
- """Delete an auto asset tag by ID - alias for delete."""
70
- return self.delete(tag_id)
71
-
72
- def start_tagging(self, request: Union[StartTaggingRequest, Dict[str, Any]]) -> TaggingResponse:
73
- """Start the tagging process for auto asset tags."""
74
- command = StartTaggingCommand(self.http_client, request)
75
- return command.execute()
1
+ """
2
+ Auto Asset Tags API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any, Union
6
+
7
+ from ..http_client import HTTPClient
8
+ from ..models.auto_asset_tags import (
9
+ AutoAssetTag, AutoAssetTagFilter, CreateAutoAssetTagRequest, UpdateAutoAssetTagRequest,
10
+ StartTaggingRequest, TaggingResponse
11
+ )
12
+ from ..queries.auto_asset_tags import ListAutoAssetTagsQuery, GetAutoAssetTagQuery
13
+ from ..commands.auto_asset_tags import (
14
+ CreateAutoAssetTagCommand, UpdateAutoAssetTagCommand, DeleteAutoAssetTagCommand,
15
+ StartTaggingCommand
16
+ )
17
+
18
+
19
+ class AutoAssetTagsAPI:
20
+ """Auto Asset Tags API with CQRS pattern - separated queries and commands."""
21
+
22
+ def __init__(self, http_client: HTTPClient):
23
+ self.http_client = http_client
24
+
25
+ # QUERIES (Read operations)
26
+ def list(self, filter_params: Optional[AutoAssetTagFilter] = None) -> List[AutoAssetTag]:
27
+ """List auto asset tags with optional filtering."""
28
+ query = ListAutoAssetTagsQuery(self.http_client, filter_params)
29
+ return query.execute()
30
+
31
+ def get(self, tag_id: str) -> AutoAssetTag:
32
+ """Get a specific auto asset tag by ID."""
33
+ query = GetAutoAssetTagQuery(self.http_client, tag_id)
34
+ return query.execute()
35
+
36
+ def get_by_id(self, tag_id: str) -> AutoAssetTag:
37
+ """Get a specific auto asset tag by ID - alias for get."""
38
+ return self.get(tag_id)
39
+
40
+ # COMMANDS (Write operations)
41
+ def create(self, request: Union[CreateAutoAssetTagRequest, Dict[str, Any]]) -> AutoAssetTag:
42
+ """Create a new auto asset tag."""
43
+ command = CreateAutoAssetTagCommand(self.http_client, request)
44
+ return command.execute()
45
+
46
+ def update(
47
+ self,
48
+ tag_id_or_data: Union[str, Dict[str, Any]],
49
+ request: Optional[Union[UpdateAutoAssetTagRequest, Dict[str, Any]]] = None
50
+ ) -> AutoAssetTag:
51
+ """Update an existing auto asset tag."""
52
+ # Handle both signatures: update(tag_id, request) and update(data_dict)
53
+ if isinstance(tag_id_or_data, str) and request is not None:
54
+ # Traditional signature: update(tag_id, request)
55
+ command = UpdateAutoAssetTagCommand(self.http_client, tag_id_or_data, request)
56
+ elif isinstance(tag_id_or_data, dict):
57
+ # Dict signature: update(data_dict) where data_dict contains 'id'
58
+ tag_id = tag_id_or_data.get('id')
59
+ if not tag_id:
60
+ raise ValueError("Tag ID must be provided in data dict or as separate parameter")
61
+ command = UpdateAutoAssetTagCommand(self.http_client, tag_id, tag_id_or_data)
62
+ else:
63
+ raise ValueError("Invalid arguments for update")
64
+
65
+ return command.execute()
66
+
67
+ def delete(self, tag_id: str) -> Dict[str, Any]:
68
+ """Delete an auto asset tag."""
69
+ command = DeleteAutoAssetTagCommand(self.http_client, tag_id)
70
+ return command.execute()
71
+
72
+ def delete_by_id(self, tag_id: str) -> Dict[str, Any]:
73
+ """Delete an auto asset tag by ID - alias for delete."""
74
+ return self.delete(tag_id)
75
+
76
+ def start_tagging(self, request: Union[StartTaggingRequest, Dict[str, Any]]) -> TaggingResponse:
77
+ """Start the tagging process for auto asset tags."""
78
+ command = StartTaggingCommand(self.http_client, request)
79
+ return command.execute()
@@ -0,0 +1,177 @@
1
+ """
2
+ Backup API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+ from datetime import datetime
7
+
8
+ from ..http_client import HTTPClient
9
+ from ..models.backup import (
10
+ BackupHistoryResponse, BackupFilter, BackupNowRequest,
11
+ BackupStatus, BackupSource, BackupLocation, BackupStats
12
+ )
13
+ from ..queries.backup import (
14
+ GetBackupHistoryQuery, GetBackupDownloadQuery
15
+ )
16
+ from ..commands.backup import (
17
+ BackupNowCommand, DeleteBackupCommand
18
+ )
19
+
20
+
21
+ class BackupAPI:
22
+ """Backup API with CQRS pattern - separated queries and commands."""
23
+
24
+ def __init__(self, http_client: HTTPClient):
25
+ self.http_client = http_client
26
+
27
+ # QUERIES (Read operations)
28
+ def get_history(self, filter_params: Optional[BackupFilter] = None) -> BackupHistoryResponse:
29
+ """Get backup history with optional filtering."""
30
+ query = GetBackupHistoryQuery(self.http_client, filter_params)
31
+ return query.execute()
32
+
33
+ def download_backup(self, backup_id: str) -> Dict[str, Any]:
34
+ """Download a backup by ID."""
35
+ query = GetBackupDownloadQuery(self.http_client, backup_id)
36
+ return query.execute()
37
+
38
+ # COMMANDS (Write operations)
39
+ def backup_now(self, request: BackupNowRequest) -> Dict[str, Any]:
40
+ """Create an immediate backup."""
41
+ command = BackupNowCommand(self.http_client, request)
42
+ return command.execute()
43
+
44
+ def delete_backup(self, backup_id: str) -> Dict[str, Any]:
45
+ """Delete a backup by ID."""
46
+ command = DeleteBackupCommand(self.http_client, backup_id)
47
+ return command.execute()
48
+
49
+ # Convenience methods
50
+ def backup_all_endpoints(self, organization_ids: Optional[List[int]] = None) -> Dict[str, Any]:
51
+ """Create a backup of all endpoints."""
52
+ request = BackupNowRequest(organizationIds=organization_ids)
53
+ return self.backup_now(request)
54
+
55
+ def backup_specific_endpoints(self, endpoint_ids: List[str],
56
+ organization_ids: Optional[List[int]] = None) -> Dict[str, Any]:
57
+ """Create a backup of specific endpoints."""
58
+ request = BackupNowRequest(
59
+ includedEndpointIds=endpoint_ids,
60
+ organizationIds=organization_ids
61
+ )
62
+ return self.backup_now(request)
63
+
64
+ def get_recent_backups(self, days: int = 7) -> BackupHistoryResponse:
65
+ """Get recent backups from the last N days."""
66
+ end_date = datetime.utcnow()
67
+ start_date = datetime.utcnow().replace(day=end_date.day - days)
68
+
69
+ filter_params = BackupFilter(
70
+ startDate=start_date,
71
+ endDate=end_date,
72
+ sortBy="startDate",
73
+ sortType="DESC"
74
+ )
75
+ return self.get_history(filter_params)
76
+
77
+ def get_failed_backups(self) -> BackupHistoryResponse:
78
+ """Get all failed backups."""
79
+ filter_params = BackupFilter(
80
+ status=BackupStatus.FAILED,
81
+ sortBy="startDate",
82
+ sortType="DESC"
83
+ )
84
+ return self.get_history(filter_params)
85
+
86
+ def get_in_progress_backups(self) -> BackupHistoryResponse:
87
+ """Get all in-progress backups."""
88
+ filter_params = BackupFilter(
89
+ status=BackupStatus.IN_PROGRESS,
90
+ sortBy="startDate",
91
+ sortType="DESC"
92
+ )
93
+ return self.get_history(filter_params)
94
+
95
+ def get_user_backups(self, username: str) -> BackupHistoryResponse:
96
+ """Get backups created by a specific user."""
97
+ filter_params = BackupFilter(
98
+ username=username,
99
+ source=BackupSource.USER,
100
+ sortBy="startDate",
101
+ sortType="DESC"
102
+ )
103
+ return self.get_history(filter_params)
104
+
105
+ def get_scheduled_backups(self) -> BackupHistoryResponse:
106
+ """Get backups created by scheduler."""
107
+ filter_params = BackupFilter(
108
+ source=BackupSource.SCHEDULER,
109
+ sortBy="startDate",
110
+ sortType="DESC"
111
+ )
112
+ return self.get_history(filter_params)
113
+
114
+ def get_backup_stats(self) -> BackupStats:
115
+ """Get backup statistics summary."""
116
+ # Get all backup history to calculate stats
117
+ all_backups = self.get_history(BackupFilter(pageSize=1000))
118
+
119
+ stats = BackupStats()
120
+ stats.total_backups = len(all_backups.entities)
121
+
122
+ total_duration = 0
123
+ backup_count_with_duration = 0
124
+
125
+ for backup in all_backups.entities:
126
+ # Count by status
127
+ if backup.status == BackupStatus.SUCCEEDED:
128
+ stats.successful_backups += 1
129
+ elif backup.status == BackupStatus.FAILED:
130
+ stats.failed_backups += 1
131
+ elif backup.status == BackupStatus.IN_PROGRESS:
132
+ stats.in_progress_backups += 1
133
+
134
+ # Sum total size
135
+ if backup.size:
136
+ stats.total_size_bytes += backup.size
137
+
138
+ # Calculate average backup time
139
+ if backup.start_date and backup.end_date:
140
+ duration = (backup.end_date - backup.start_date).total_seconds() / 60
141
+ total_duration += duration
142
+ backup_count_with_duration += 1
143
+
144
+ # Track last backup date
145
+ if not stats.last_backup_date or backup.start_date > stats.last_backup_date:
146
+ stats.last_backup_date = backup.start_date
147
+
148
+ # Calculate average backup time
149
+ if backup_count_with_duration > 0:
150
+ stats.average_backup_time_minutes = total_duration / backup_count_with_duration
151
+
152
+ return stats
153
+
154
+ def cleanup_old_backups(self, days_to_keep: int = 30) -> List[str]:
155
+ """Delete backups older than specified days and return deleted backup IDs."""
156
+ cutoff_date = datetime.utcnow().replace(day=datetime.utcnow().day - days_to_keep)
157
+
158
+ filter_params = BackupFilter(
159
+ endDate=cutoff_date,
160
+ status=BackupStatus.SUCCEEDED,
161
+ sortBy="startDate",
162
+ sortType="ASC",
163
+ pageSize=1000
164
+ )
165
+
166
+ old_backups = self.get_history(filter_params)
167
+ deleted_ids = []
168
+
169
+ for backup in old_backups.entities:
170
+ try:
171
+ self.delete_backup(backup.id)
172
+ deleted_ids.append(backup.id)
173
+ except Exception:
174
+ # Continue with other backups if one fails
175
+ continue
176
+
177
+ return deleted_ids
@@ -0,0 +1,46 @@
1
+ """
2
+ Baseline 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 ..commands.baseline import (
8
+ AcquireBaselineByFilterCommand,
9
+ CompareBaselineByEndpointCommand,
10
+ )
11
+ from ..queries.baseline import (
12
+ GetBaselineComparisonReportQuery,
13
+ )
14
+
15
+
16
+ class BaselineAPI:
17
+ """Baseline API with CQRS pattern - separated queries and commands."""
18
+
19
+ def __init__(self, http_client: HTTPClient):
20
+ self.http_client = http_client
21
+
22
+ # COMMANDS (Write operations)
23
+ def acquire_by_filter(self, filter_data: Dict[str, Any], case_id: Optional[str] = None) -> Dict[str, Any]:
24
+ """Acquire baselines by asset filter criteria."""
25
+ payload = {
26
+ "filter": filter_data,
27
+ "caseId": case_id
28
+ }
29
+
30
+ command = AcquireBaselineByFilterCommand(self.http_client, payload)
31
+ return command.execute()
32
+
33
+ def compare_by_endpoint(self, endpoint_id: str, baseline_task_ids: List[str]) -> Dict[str, Any]:
34
+ """Compare baseline acquisition tasks by endpoint ID."""
35
+ payload = {
36
+ "endpointId": endpoint_id,
37
+ "taskIds": baseline_task_ids
38
+ }
39
+
40
+ command = CompareBaselineByEndpointCommand(self.http_client, payload)
41
+ return command.execute()
42
+
43
+ def get_comparison_report(self, endpoint_id: str, task_id: str) -> Dict[str, Any]:
44
+ """Get comparison report by endpoint ID and task ID."""
45
+ query = GetBaselineComparisonReportQuery(self.http_client, endpoint_id, task_id)
46
+ return query.execute()
@@ -0,0 +1,225 @@
1
+ """
2
+ Cases 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.cases import (
8
+ Case, CaseActivity, CaseEndpoint, CaseTask, User, CaseFilter, CaseActivityFilter,
9
+ CreateCaseRequest, UpdateCaseRequest, CaseNote, CaseEndpointFilter, CaseTaskFilter, CaseUserFilter
10
+ )
11
+ from ..models.assets import AssetFilter
12
+ from ..queries.cases import (
13
+ ListCasesQuery,
14
+ GetCaseQuery,
15
+ GetCaseActivitiesQuery,
16
+ GetCaseEndpointsQuery,
17
+ GetCaseTasksQuery,
18
+ GetCaseUsersQuery,
19
+ CheckCaseNameQuery,
20
+ )
21
+ from ..commands.cases import (
22
+ CreateCaseCommand,
23
+ UpdateCaseCommand,
24
+ CloseCaseCommand,
25
+ OpenCaseCommand,
26
+ ArchiveCaseCommand,
27
+ ChangeCaseOwnerCommand,
28
+ RemoveEndpointsFromCaseCommand,
29
+ RemoveTaskAssignmentFromCaseCommand,
30
+ ImportTaskAssignmentsToCaseCommand,
31
+ AddNoteToCaseCommand,
32
+ UpdateNoteToCaseCommand,
33
+ DeleteNoteToCaseCommand,
34
+ ExportCaseNotesCommand,
35
+ ExportCasesCommand,
36
+ ExportCaseEndpointsCommand,
37
+ ExportCaseActivitiesCommand,
38
+ )
39
+
40
+
41
+ class CasesAPI:
42
+ """Cases API with CQRS pattern - separated queries and commands."""
43
+
44
+ def __init__(self, http_client: HTTPClient):
45
+ self.http_client = http_client
46
+
47
+ # QUERIES (Read operations)
48
+ def list(self, filter_params: Optional[CaseFilter] = None, organization_ids: Optional[List[int]] = None) -> List[Case]:
49
+ """List cases with optional filtering."""
50
+ query = ListCasesQuery(self.http_client, filter_params, organization_ids)
51
+ return query.execute()
52
+
53
+ def get(self, case_id: str) -> Case:
54
+ """Get a specific case by ID."""
55
+ query = GetCaseQuery(self.http_client, case_id)
56
+ return query.execute()
57
+
58
+ def get_activities(self, case_id: str, filter_params: Optional[CaseActivityFilter] = None) -> List[CaseActivity]:
59
+ """Get activities for a specific case with optional filtering, pagination, and sorting."""
60
+ query = GetCaseActivitiesQuery(self.http_client, case_id, filter_params)
61
+ return query.execute()
62
+
63
+ def get_endpoints(
64
+ self, case_id: str, filter_params: Optional[CaseEndpointFilter] = None, organization_ids: Optional[List[int]] = None
65
+ ) -> List[CaseEndpoint]:
66
+ """Get endpoints for a specific case with comprehensive filtering support.
67
+
68
+ Args:
69
+ case_id: The case ID to get endpoints for
70
+ filter_params: Optional CaseEndpointFilter with comprehensive filtering options
71
+ organization_ids: Optional list of organization IDs (for backward compatibility)
72
+
73
+ Returns:
74
+ List of CaseEndpoint objects
75
+
76
+ Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
77
+ """
78
+ # Handle backward compatibility
79
+ if filter_params is None:
80
+ filter_params = CaseEndpointFilter()
81
+
82
+ # If organization_ids is provided and not set in filter_params, use it
83
+ if organization_ids is not None and filter_params.organization_ids is None:
84
+ filter_params.organization_ids = organization_ids
85
+
86
+ query = GetCaseEndpointsQuery(self.http_client, case_id, filter_params)
87
+ return query.execute()
88
+
89
+ def get_tasks(
90
+ self, case_id: str, filter_params: Optional[CaseTaskFilter] = None, organization_ids: Optional[List[int]] = None
91
+ ) -> List[CaseTask]:
92
+ """Get tasks for a specific case with comprehensive filtering support.
93
+
94
+ Args:
95
+ case_id: The case ID to get tasks for
96
+ filter_params: Optional CaseTaskFilter with comprehensive filtering options
97
+ organization_ids: Optional list of organization IDs (for backward compatibility)
98
+
99
+ Returns:
100
+ List of CaseTask objects
101
+
102
+ Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
103
+ """
104
+ # Handle backward compatibility
105
+ if filter_params is None:
106
+ filter_params = CaseTaskFilter()
107
+
108
+ # If organization_ids is provided and not set in filter_params, use it
109
+ if organization_ids is not None and filter_params.organization_ids is None:
110
+ filter_params.organization_ids = organization_ids
111
+
112
+ query = GetCaseTasksQuery(self.http_client, case_id, filter_params)
113
+ return query.execute()
114
+
115
+ def get_users(
116
+ self, case_id: str, filter_params: Optional[CaseUserFilter] = None, organization_ids: Optional[List[int]] = None
117
+ ) -> List[User]:
118
+ """Get users for a specific case with comprehensive filtering support.
119
+
120
+ Args:
121
+ case_id: The case ID to get users for
122
+ filter_params: Optional CaseUserFilter with comprehensive filtering options
123
+ organization_ids: Optional list of organization IDs (for backward compatibility)
124
+
125
+ Returns:
126
+ List of User objects
127
+
128
+ Note: If both filter_params and organization_ids are provided, organization_ids in filter_params takes precedence.
129
+ """
130
+ # Handle backward compatibility
131
+ if filter_params is None:
132
+ filter_params = CaseUserFilter()
133
+
134
+ # If organization_ids is provided and not set in filter_params, use it
135
+ if organization_ids is not None and filter_params.organization_ids is None:
136
+ filter_params.organization_ids = organization_ids
137
+
138
+ query = GetCaseUsersQuery(self.http_client, case_id, filter_params)
139
+ return query.execute()
140
+
141
+ def check_name(self, name: str) -> bool:
142
+ """Check if a case name is available."""
143
+ query = CheckCaseNameQuery(self.http_client, name)
144
+ return query.execute()
145
+
146
+ # COMMANDS (Write operations)
147
+ def create(self, case_data: CreateCaseRequest) -> Case:
148
+ """Create a new case."""
149
+ command = CreateCaseCommand(self.http_client, case_data)
150
+ return command.execute()
151
+
152
+ def update(self, case_id: str, update_data: UpdateCaseRequest) -> Case:
153
+ """Update an existing case."""
154
+ command = UpdateCaseCommand(self.http_client, case_id, update_data)
155
+ return command.execute()
156
+
157
+ def close(self, case_id: str) -> Case:
158
+ """Close a case."""
159
+ command = CloseCaseCommand(self.http_client, case_id)
160
+ return command.execute()
161
+
162
+ def open(self, case_id: str) -> Case:
163
+ """Open a case."""
164
+ command = OpenCaseCommand(self.http_client, case_id)
165
+ return command.execute()
166
+
167
+ def archive(self, case_id: str) -> Case:
168
+ """Archive a case."""
169
+ command = ArchiveCaseCommand(self.http_client, case_id)
170
+ return command.execute()
171
+
172
+ def change_owner(self, case_id: str, new_owner_id: str) -> Case:
173
+ """Change case owner."""
174
+ command = ChangeCaseOwnerCommand(self.http_client, case_id, new_owner_id)
175
+ return command.execute()
176
+
177
+ def remove_endpoints(self, case_id: str, filter_params: AssetFilter) -> Dict[str, Any]:
178
+ """Remove endpoints from a case."""
179
+ command = RemoveEndpointsFromCaseCommand(self.http_client, case_id, filter_params)
180
+ return command.execute()
181
+
182
+ def remove_task_assignment(self, case_id: str, task_assignment_id: str) -> Dict[str, Any]:
183
+ """Remove task assignment from a case."""
184
+ command = RemoveTaskAssignmentFromCaseCommand(self.http_client, case_id, task_assignment_id)
185
+ return command.execute()
186
+
187
+ def import_task_assignments(self, case_id: str, task_assignment_ids: List[str]) -> Dict[str, Any]:
188
+ """Import task assignments to a case."""
189
+ command = ImportTaskAssignmentsToCaseCommand(self.http_client, case_id, task_assignment_ids)
190
+ return command.execute()
191
+
192
+ def add_note(self, case_id: str, note_value: str) -> CaseNote:
193
+ """Add a note to a case."""
194
+ command = AddNoteToCaseCommand(self.http_client, case_id, note_value)
195
+ return command.execute()
196
+
197
+ def update_note(self, case_id: str, note_id: str, note_value: str) -> CaseNote:
198
+ """Update a note in a case."""
199
+ command = UpdateNoteToCaseCommand(self.http_client, case_id, note_id, note_value)
200
+ return command.execute()
201
+
202
+ def delete_note(self, case_id: str, note_id: str) -> Dict[str, Any]:
203
+ """Delete a note from a case."""
204
+ command = DeleteNoteToCaseCommand(self.http_client, case_id, note_id)
205
+ return command.execute()
206
+
207
+ def export_notes(self, case_id: str) -> Dict[str, Any]:
208
+ """Export case notes as a file download (ZIP/CSV format)."""
209
+ command = ExportCaseNotesCommand(self.http_client, case_id)
210
+ return command.execute()
211
+
212
+ def export_cases(self, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
213
+ """Export cases as a CSV file download."""
214
+ command = ExportCasesCommand(self.http_client, filter_params)
215
+ return command.execute()
216
+
217
+ def export_endpoints(self, case_id: str, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
218
+ """Export case endpoints as a CSV file download with optional filtering."""
219
+ command = ExportCaseEndpointsCommand(self.http_client, case_id, filter_params)
220
+ return command.execute()
221
+
222
+ def export_activities(self, case_id: str, filter_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
223
+ """Export case activities as a CSV file download with optional filtering and pagination."""
224
+ command = ExportCaseActivitiesCommand(self.http_client, case_id, filter_params)
225
+ return command.execute()