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,115 @@
1
+ """
2
+ Params queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Dict, Any, Union
6
+
7
+ from ..base import Query
8
+ from ..models.params import (
9
+ AcquisitionArtifact, EDiscoveryPattern, AcquisitionEvidence, DroneAnalyzer,
10
+ AcquisitionArtifactsResponse, EDiscoveryCategory, AcquisitionEvidencesResponse
11
+ )
12
+ from ..http_client import HTTPClient
13
+
14
+
15
+ class GetAcquisitionArtifactsQuery(Query[List[AcquisitionArtifact]]):
16
+ """Query to get acquisition artifacts."""
17
+
18
+ def __init__(self, http_client: HTTPClient):
19
+ self.http_client = http_client
20
+
21
+ def execute(self) -> List[AcquisitionArtifact]:
22
+ """Execute the query to get acquisition artifacts."""
23
+ response: Dict[str, Any] = self.http_client.get("params/acquisition/artifacts")
24
+
25
+ # Parse using Pydantic models
26
+ artifacts_response = AcquisitionArtifactsResponse.model_validate(response)
27
+
28
+ # Flatten the structure into a single list
29
+ all_artifacts = []
30
+
31
+ # Process all platforms
32
+ for platform_name, groups in [
33
+ ("windows", artifacts_response.windows),
34
+ ("linux", artifacts_response.linux),
35
+ ("macos", artifacts_response.macos),
36
+ ("aix", artifacts_response.aix)
37
+ ]:
38
+ for group in groups:
39
+ for artifact in group.items:
40
+ artifact.group = group.group
41
+ artifact.platform = platform_name
42
+ all_artifacts.append(artifact)
43
+
44
+ return all_artifacts
45
+
46
+
47
+ class GetEDiscoveryPatternsQuery(Query[List[EDiscoveryPattern]]):
48
+ """Query to get e-discovery patterns."""
49
+
50
+ def __init__(self, http_client: HTTPClient):
51
+ self.http_client = http_client
52
+
53
+ def execute(self) -> List[EDiscoveryPattern]:
54
+ """Execute the query to get e-discovery patterns."""
55
+ response = self.http_client.get("params/acquisition/e-discovery-patterns")
56
+
57
+ # Parse using Pydantic models
58
+ categories = [EDiscoveryCategory.model_validate(item) for item in response]
59
+
60
+ # Flatten the structure into a single list
61
+ all_patterns = []
62
+ for category in categories:
63
+ for pattern in category.applications:
64
+ pattern.category = category.category
65
+ all_patterns.append(pattern)
66
+
67
+ return all_patterns
68
+
69
+
70
+ class GetAcquisitionEvidencesQuery(Query[List[AcquisitionEvidence]]):
71
+ """Query to get acquisition evidences."""
72
+
73
+ def __init__(self, http_client: HTTPClient):
74
+ self.http_client = http_client
75
+
76
+ def execute(self) -> List[AcquisitionEvidence]:
77
+ """Execute the query to get acquisition evidences."""
78
+ response: Dict[str, Any] = self.http_client.get("params/acquisition/evidences")
79
+
80
+ # Parse using Pydantic models
81
+ evidences_response = AcquisitionEvidencesResponse.model_validate(response)
82
+
83
+ # Flatten the structure into a single list
84
+ all_evidences = []
85
+
86
+ # Process all platforms
87
+ for platform_name, groups in [
88
+ ("windows", evidences_response.windows),
89
+ ("linux", evidences_response.linux),
90
+ ("macos", evidences_response.macos),
91
+ ("aix", evidences_response.aix)
92
+ ]:
93
+ for group in groups:
94
+ for evidence in group.items:
95
+ evidence.group = group.group
96
+ evidence.platform = platform_name
97
+ all_evidences.append(evidence)
98
+
99
+ return all_evidences
100
+
101
+
102
+ class GetDroneAnalyzersQuery(Query[List[DroneAnalyzer]]):
103
+ """Query to get drone analyzers."""
104
+
105
+ def __init__(self, http_client: HTTPClient):
106
+ self.http_client = http_client
107
+
108
+ def execute(self) -> List[DroneAnalyzer]:
109
+ """Execute the query to get drone analyzers."""
110
+ response = self.http_client.get("params/drone/analyzers")
111
+
112
+ # Parse using Pydantic models with automatic field mapping
113
+ analyzers = [DroneAnalyzer.model_validate(item) for item in response]
114
+
115
+ return analyzers
@@ -0,0 +1,150 @@
1
+ """
2
+ Policy-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.policies import (
9
+ Policy, PolicyFilter, PolicyAssignment, PolicyExecution,
10
+ PolicyRule, PolicyCondition, PolicyAction,
11
+ PoliciesPaginatedResponse, PolicyMatchStats
12
+ )
13
+ from ..http_client import HTTPClient
14
+
15
+
16
+ class ListPoliciesQuery(Query[PoliciesPaginatedResponse]):
17
+ """Query to list policies with optional filtering."""
18
+
19
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[PolicyFilter] = None, organization_ids: Optional[List[int]] = None):
20
+ self.http_client = http_client
21
+ self.filter_params = filter_params or PolicyFilter()
22
+
23
+ # Fix API-001: Ensure organizationIds are always provided to prevent 500 error
24
+ if organization_ids is None or len(organization_ids) == 0:
25
+ self.organization_ids = [0] # Default to organization 0
26
+ else:
27
+ self.organization_ids = organization_ids
28
+
29
+ def execute(self) -> PoliciesPaginatedResponse:
30
+ """Execute the query to list policies."""
31
+ # Validate organization_ids before making API call
32
+ if not self.organization_ids or len(self.organization_ids) == 0:
33
+ from ..exceptions import ValidationError
34
+ raise ValidationError(
35
+ "organizationIds parameter is required for listing policies. "
36
+ "Please provide at least one organization ID."
37
+ )
38
+
39
+ params = self.filter_params.to_params()
40
+
41
+ # Add organization IDs
42
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
43
+
44
+ response = self.http_client.get("policies", params=params)
45
+
46
+ # Parse using Pydantic models with automatic field mapping
47
+ return PoliciesPaginatedResponse.model_validate(response.get("result", {}))
48
+
49
+
50
+ class GetPolicyQuery(Query[Policy]):
51
+ """Query to get a specific policy by ID."""
52
+
53
+ def __init__(self, http_client: HTTPClient, policy_id: str):
54
+ self.http_client = http_client
55
+ self.policy_id = policy_id
56
+
57
+ def execute(self) -> Policy:
58
+ """Execute the query to get policy details."""
59
+ response = self.http_client.get(f"policies/{self.policy_id}")
60
+
61
+ # Parse using Pydantic models with automatic field mapping
62
+ return Policy.model_validate(response.get("result", {}))
63
+
64
+
65
+ class GetPolicyAssignmentsQuery(Query[List[PolicyAssignment]]):
66
+ """Query to get policy assignments."""
67
+
68
+ def __init__(self, http_client: HTTPClient, policy_id: str):
69
+ self.http_client = http_client
70
+ self.policy_id = policy_id
71
+
72
+ def execute(self) -> List[PolicyAssignment]:
73
+ """Execute the query to get policy assignments."""
74
+ response = self.http_client.get(f"policies/{self.policy_id}/assignments")
75
+
76
+ entities = response.get("result", {}).get("entities", [])
77
+
78
+ assignments = []
79
+ for entity_data in entities:
80
+ mapped_data = {
81
+ "id": entity_data.get("_id"),
82
+ "policy_id": entity_data.get("policyId"),
83
+ "endpoint_id": entity_data.get("endpointId"),
84
+ "assigned_at": entity_data.get("assignedAt"),
85
+ "assigned_by": entity_data.get("assignedBy"),
86
+ "status": entity_data.get("status", "active"),
87
+ }
88
+
89
+ # Remove None values
90
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
91
+
92
+ assignments.append(PolicyAssignment(**mapped_data))
93
+
94
+ return assignments
95
+
96
+
97
+ class GetPolicyExecutionsQuery(Query[List[PolicyExecution]]):
98
+ """Query to get policy execution history."""
99
+
100
+ def __init__(self, http_client: HTTPClient, policy_id: str, limit: Optional[int] = None):
101
+ self.http_client = http_client
102
+ self.policy_id = policy_id
103
+ self.limit = limit
104
+
105
+ def execute(self) -> List[PolicyExecution]:
106
+ """Execute the query to get policy executions."""
107
+ params = {}
108
+ if self.limit:
109
+ params["limit"] = str(self.limit)
110
+
111
+ response = self.http_client.get(f"policies/{self.policy_id}/executions", params=params)
112
+
113
+ entities = response.get("result", {}).get("entities", [])
114
+
115
+ executions = []
116
+ for entity_data in entities:
117
+ mapped_data = {
118
+ "id": entity_data.get("_id"),
119
+ "policy_id": entity_data.get("policyId"),
120
+ "endpoint_id": entity_data.get("endpointId"),
121
+ "executed_at": entity_data.get("executedAt"),
122
+ "status": entity_data.get("status"),
123
+ "result": entity_data.get("result", {}),
124
+ "errors": entity_data.get("errors", []),
125
+ "duration": entity_data.get("duration"),
126
+ }
127
+
128
+ # Remove None values
129
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
130
+
131
+ executions.append(PolicyExecution(**mapped_data))
132
+
133
+ return executions
134
+
135
+
136
+ class GetPolicyMatchStatsQuery(Query[PolicyMatchStats]):
137
+ """Query to get policy match statistics."""
138
+
139
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[PolicyFilter] = None):
140
+ self.http_client = http_client
141
+ self.filter_params = filter_params or PolicyFilter()
142
+
143
+ def execute(self) -> PolicyMatchStats:
144
+ """Execute the query to get policy match statistics."""
145
+ params = self.filter_params.to_params()
146
+
147
+ response = self.http_client.get("policies/match-stats", params=params)
148
+
149
+ # Parse using Pydantic models with automatic field mapping
150
+ return PolicyMatchStats.model_validate(response.get("result", {}))
@@ -0,0 +1,20 @@
1
+ """
2
+ Settings queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from ..base import Query
6
+ from ..models.settings import BannerSettings
7
+ from ..http_client import HTTPClient
8
+
9
+
10
+ class GetBannerSettingsQuery(Query[BannerSettings]):
11
+ """Query to get banner settings."""
12
+
13
+ def __init__(self, http_client: HTTPClient):
14
+ self.http_client = http_client
15
+
16
+ def execute(self) -> BannerSettings:
17
+ """Execute the query to get banner settings."""
18
+ response = self.http_client.get("settings/banner")
19
+
20
+ return BannerSettings(**response.get("result", {}))
@@ -0,0 +1,82 @@
1
+ """
2
+ Task-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.tasks import Task, TaskFilter, TaskData, PlatformEvidenceConfig, TaskConfig, DroneConfig, TaskAssignment
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class ListTasksQuery(Query[List[Task]]):
13
+ """Query to list tasks with optional filtering."""
14
+
15
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[TaskFilter] = None, organization_ids: Optional[List[int]] = None):
16
+ self.http_client = http_client
17
+ self.filter_params = filter_params or TaskFilter()
18
+ self.organization_ids = organization_ids or [0]
19
+
20
+ def execute(self) -> List[Task]:
21
+ """Execute the query to list tasks."""
22
+ params = self.filter_params.to_params()
23
+
24
+ # Add organization IDs
25
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
26
+
27
+ # Ensure consistent sorting to match API defaults
28
+ if "sortBy" not in params:
29
+ params["sortBy"] = "createdAt"
30
+ if "sortType" not in params:
31
+ params["sortType"] = "ASC"
32
+
33
+ response = self.http_client.get("tasks", params=params)
34
+
35
+ entities = response.get("result", {}).get("entities", [])
36
+
37
+ # Use Pydantic parsing with proper field aliasing
38
+ tasks = []
39
+ for entity_data in entities:
40
+ task = Task.model_validate(entity_data)
41
+ tasks.append(task)
42
+
43
+ return tasks
44
+
45
+
46
+ class GetTaskQuery(Query[Task]):
47
+ """Query to get a specific task by ID."""
48
+
49
+ def __init__(self, http_client: HTTPClient, task_id: str):
50
+ self.http_client = http_client
51
+ self.task_id = task_id
52
+
53
+ def execute(self) -> Task:
54
+ """Execute the query to get a task."""
55
+ response = self.http_client.get(f"tasks/{self.task_id}")
56
+
57
+ task_data = response.get("result", {})
58
+
59
+ # Use Pydantic parsing with proper field aliasing
60
+ return Task.model_validate(task_data)
61
+
62
+
63
+ class GetTaskAssignmentsQuery(Query[List[TaskAssignment]]):
64
+ """Query to get task assignments for a specific task."""
65
+
66
+ def __init__(self, http_client: HTTPClient, task_id: str):
67
+ self.http_client = http_client
68
+ self.task_id = task_id
69
+
70
+ def execute(self) -> List[TaskAssignment]:
71
+ """Execute the query to get task assignments."""
72
+ response = self.http_client.get(f"tasks/{self.task_id}/assignments")
73
+
74
+ entities = response.get("result", {}).get("entities", [])
75
+
76
+ # Use Pydantic parsing with proper field aliasing
77
+ assignments = []
78
+ for entity_data in entities:
79
+ assignment = TaskAssignment.model_validate(entity_data)
80
+ assignments.append(assignment)
81
+
82
+ return assignments
@@ -0,0 +1,231 @@
1
+ """
2
+ Triage-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from ..base import Query
8
+ from ..models.triage import (
9
+ TriageRule, TriageTag, TriageFilter, TriageRuleType, TriageSeverity, TriageStatus
10
+ )
11
+ from ..http_client import HTTPClient
12
+
13
+
14
+ class ListTriageRulesQuery(Query[List[TriageRule]]):
15
+ """Query to list triage rules with optional filtering."""
16
+
17
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[TriageFilter] = None, organization_ids: Optional[List[int]] = None):
18
+ self.http_client = http_client
19
+ self.filter_params = filter_params or TriageFilter()
20
+ self.organization_ids = organization_ids or [0]
21
+
22
+ def execute(self) -> List[TriageRule]:
23
+ """Execute the query to list triage rules."""
24
+ params = self.filter_params.to_params()
25
+
26
+ # Add organization IDs
27
+ params["filter[organizationIds]"] = ",".join(map(str, self.organization_ids))
28
+
29
+ response = self.http_client.get("triages/rules", params=params)
30
+
31
+ entities = response.get("result", {}).get("entities", [])
32
+
33
+ rules = []
34
+ for entity_data in entities:
35
+ mapped_data = {
36
+ "id": entity_data.get("_id"),
37
+ "name": entity_data.get("description", ""), # Use description as name
38
+ "description": entity_data.get("description"),
39
+ "type": entity_data.get("engine", "yara"), # Use engine as type
40
+ "rule_content": entity_data.get("rule", ""), # Try "rule" field
41
+ "enabled": entity_data.get("enabled", True),
42
+ "severity": entity_data.get("severity", "medium"),
43
+ "tags": entity_data.get("tags", []),
44
+ "search_in": entity_data.get("searchIn"), # Map searchIn field
45
+ "organization_id": entity_data.get("organizationId", 0),
46
+ "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
47
+ "created_at": entity_data.get("createdAt"),
48
+ "updated_at": entity_data.get("updatedAt"),
49
+ "created_by": entity_data.get("createdBy", "Unknown"),
50
+ "updated_by": entity_data.get("updatedBy"),
51
+ "match_count": entity_data.get("matchCount", 0),
52
+ "last_match": entity_data.get("lastMatch"),
53
+ "deletable": entity_data.get("deletable"), # Map deletable field
54
+ }
55
+
56
+ # Remove None values
57
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
58
+
59
+ rules.append(TriageRule(**mapped_data))
60
+
61
+ return rules
62
+
63
+
64
+ class GetTriageRuleQuery(Query[TriageRule]):
65
+ """Query to get a specific triage rule by ID."""
66
+
67
+ def __init__(self, http_client: HTTPClient, rule_id: str):
68
+ self.http_client = http_client
69
+ self.rule_id = rule_id
70
+
71
+ def execute(self) -> TriageRule:
72
+ """Execute the query to get triage rule details."""
73
+ response = self.http_client.get(f"triages/rules/{self.rule_id}")
74
+
75
+ entity_data = response.get("result", {})
76
+
77
+ mapped_data = {
78
+ "id": entity_data.get("_id"),
79
+ "name": entity_data.get("description", ""), # Use description as name
80
+ "description": entity_data.get("description"),
81
+ "type": entity_data.get("engine", "yara"), # Use engine as type
82
+ "rule_content": entity_data.get("rule", ""), # Try "rule" field
83
+ "enabled": entity_data.get("enabled", True),
84
+ "severity": entity_data.get("severity", "medium"),
85
+ "tags": entity_data.get("tags", []),
86
+ "search_in": entity_data.get("searchIn"), # Map searchIn field
87
+ "organization_id": entity_data.get("organizationId", 0),
88
+ "organization_ids": entity_data.get("organizationIds", []), # Map organizationIds array
89
+ "created_at": entity_data.get("createdAt"),
90
+ "updated_at": entity_data.get("updatedAt"),
91
+ "created_by": entity_data.get("createdBy", "Unknown"),
92
+ "updated_by": entity_data.get("updatedBy"),
93
+ "match_count": entity_data.get("matchCount", 0),
94
+ "last_match": entity_data.get("lastMatch"),
95
+ "deletable": entity_data.get("deletable"), # Map deletable field
96
+ }
97
+
98
+ # Remove None values
99
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
100
+
101
+ return TriageRule(**mapped_data)
102
+
103
+
104
+ class GetTriageResultsQuery(Query[Dict[str, Any]]):
105
+ """Query to get triage results for a specific task or rule."""
106
+
107
+ def __init__(self, http_client: HTTPClient, task_id: Optional[str] = None, rule_id: Optional[str] = None):
108
+ self.http_client = http_client
109
+ self.task_id = task_id
110
+ self.rule_id = rule_id
111
+
112
+ def execute(self) -> Dict[str, Any]:
113
+ """Execute the query to get triage results."""
114
+ params = {}
115
+
116
+ if self.task_id:
117
+ params["taskId"] = self.task_id
118
+ if self.rule_id:
119
+ params["ruleId"] = self.rule_id
120
+
121
+ response = self.http_client.get("triages/results", params=params)
122
+ return response.get("result", {})
123
+
124
+
125
+ class GetTriageMatchesQuery(Query[Dict[str, Any]]):
126
+ """Query to get triage matches for analysis."""
127
+
128
+ def __init__(self, http_client: HTTPClient, endpoint_id: str, task_id: str):
129
+ self.http_client = http_client
130
+ self.endpoint_id = endpoint_id
131
+ self.task_id = task_id
132
+
133
+ def execute(self) -> Dict[str, Any]:
134
+ """Execute the query to get triage matches."""
135
+ params = {
136
+ "endpointId": self.endpoint_id,
137
+ "taskId": self.task_id
138
+ }
139
+
140
+ response = self.http_client.get("triages/matches", params=params)
141
+ return response.get("result", {})
142
+
143
+
144
+ class ListTriageTagsQuery(Query[List[TriageTag]]):
145
+ """Query to list triage tags."""
146
+
147
+ def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
148
+ self.http_client = http_client
149
+ self.organization_id = organization_id or 0
150
+
151
+ def execute(self) -> List[TriageTag]:
152
+ """Execute the query to list triage tags."""
153
+ # Use singular organizationId as per API documentation
154
+ params = {"filter[organizationId]": str(self.organization_id)}
155
+
156
+ response = self.http_client.get("triages/tags", params=params)
157
+
158
+ # Handle different response formats: direct list or wrapped in result
159
+ if isinstance(response, list):
160
+ # Direct list response format
161
+ entities = response
162
+ elif isinstance(response, dict):
163
+ # Wrapped response format - check if result is list or dict
164
+ result_data = response.get("result", [])
165
+ if isinstance(result_data, list):
166
+ # result is a direct list of entities
167
+ entities = result_data
168
+ elif isinstance(result_data, dict):
169
+ # result is a dict with entities inside
170
+ entities = result_data.get("entities", [])
171
+ else:
172
+ entities = []
173
+
174
+ # Fallback: if no entities found, try direct entities field
175
+ if not entities and "entities" in response:
176
+ entities = response.get("entities", [])
177
+ else:
178
+ # Unknown format
179
+ entities = []
180
+
181
+ tags = []
182
+ for entity_data in entities:
183
+ # Ensure entity_data is a dictionary
184
+ if not isinstance(entity_data, dict):
185
+ continue
186
+
187
+ mapped_data = {
188
+ "id": entity_data.get("id") or entity_data.get("_id"), # Handle both id and _id
189
+ "name": entity_data.get("name"),
190
+ "description": entity_data.get("description"),
191
+ "color": entity_data.get("color", "#3498db"),
192
+ "created_at": entity_data.get("createdAt"),
193
+ "created_by": entity_data.get("createdBy", "Unknown"), # Provide default for required field
194
+ "organization_id": entity_data.get("organizationId", 0),
195
+ "usage_count": entity_data.get("usageCount") or entity_data.get("count", 0), # Handle both count and usageCount
196
+ }
197
+
198
+ # Remove None values but keep defaults for required fields
199
+ mapped_data = {k: v for k, v in mapped_data.items() if v is not None}
200
+
201
+ tags.append(TriageTag(**mapped_data))
202
+
203
+ return tags
204
+
205
+
206
+ class ListTriageProfilesQuery(Query[List[Dict[str, Any]]]):
207
+ """Query to list triage profiles."""
208
+
209
+ def __init__(self, http_client: HTTPClient, organization_id: Optional[int] = None):
210
+ self.http_client = http_client
211
+ self.organization_id = organization_id or 0
212
+
213
+ def execute(self) -> List[Dict[str, Any]]:
214
+ """Execute the query to list triage profiles."""
215
+ params = {"filter[organizationId]": str(self.organization_id)}
216
+
217
+ response = self.http_client.get("triages/profiles", params=params)
218
+ return response.get("result", {}).get("entities", [])
219
+
220
+
221
+ class GetTriageProfileQuery(Query[Dict[str, Any]]):
222
+ """Query to get a specific triage profile by ID."""
223
+
224
+ def __init__(self, http_client: HTTPClient, profile_id: str):
225
+ self.http_client = http_client
226
+ self.profile_id = profile_id
227
+
228
+ def execute(self) -> Dict[str, Any]:
229
+ """Execute the query to get triage profile details."""
230
+ response = self.http_client.get(f"triages/profiles/{self.profile_id}")
231
+ return response.get("result", {})
@@ -0,0 +1,83 @@
1
+ """
2
+ User Management-related queries for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from ..base import Query
8
+ from ..models.user_management import UserManagementUser, AIUser, APIUser, UserFilter
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class ListUsersQuery(Query[List[UserManagementUser]]):
13
+ """Query to list users."""
14
+
15
+ def __init__(self, http_client: HTTPClient, filter_params: Optional[UserFilter] = None):
16
+ self.http_client = http_client
17
+ self.filter_params = filter_params
18
+
19
+ def execute(self) -> List[UserManagementUser]:
20
+ """Execute the list users query."""
21
+ params = {}
22
+ if self.filter_params:
23
+ params = self.filter_params.model_dump(exclude_none=True)
24
+
25
+ response = self.http_client.get("user-management/users", params=params)
26
+
27
+ if response.get("success"):
28
+ users_data = response.get("result", {}).get("entities", [])
29
+ return [UserManagementUser(**user) for user in users_data]
30
+
31
+ return []
32
+
33
+
34
+ class GetUserQuery(Query[UserManagementUser]):
35
+ """Query to get user by ID."""
36
+
37
+ def __init__(self, http_client: HTTPClient, user_id: str):
38
+ self.http_client = http_client
39
+ self.user_id = user_id
40
+
41
+ def execute(self) -> UserManagementUser:
42
+ """Execute the get user query."""
43
+ response = self.http_client.get(f"user-management/users/{self.user_id}")
44
+
45
+ if response.get("success"):
46
+ user_data = response.get("result", {})
47
+ return UserManagementUser(**user_data)
48
+
49
+ raise Exception(f"User not found: {self.user_id}")
50
+
51
+
52
+ class GetAIUserQuery(Query[AIUser]):
53
+ """Query to get AI user."""
54
+
55
+ def __init__(self, http_client: HTTPClient):
56
+ self.http_client = http_client
57
+
58
+ def execute(self) -> AIUser:
59
+ """Execute the get AI user query."""
60
+ response = self.http_client.get("user-management/users/ai-user")
61
+
62
+ if response.get("success"):
63
+ ai_user_data = response.get("result", {})
64
+ return AIUser(**ai_user_data)
65
+
66
+ raise Exception("AI user not found")
67
+
68
+
69
+ class GetAPIUserQuery(Query[APIUser]):
70
+ """Query to get API user."""
71
+
72
+ def __init__(self, http_client: HTTPClient):
73
+ self.http_client = http_client
74
+
75
+ def execute(self) -> APIUser:
76
+ """Execute the get API user query."""
77
+ response = self.http_client.get("user-management/users/api-user")
78
+
79
+ if response.get("success"):
80
+ api_user_data = response.get("result", {})
81
+ return APIUser(**api_user_data)
82
+
83
+ raise Exception("API user not found")