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.
- binalyze_air/__init__.py +77 -0
- binalyze_air/apis/__init__.py +27 -0
- binalyze_air/apis/authentication.py +27 -0
- binalyze_air/apis/auto_asset_tags.py +75 -0
- binalyze_air/apis/endpoints.py +22 -0
- binalyze_air/apis/event_subscription.py +97 -0
- binalyze_air/apis/evidence.py +53 -0
- binalyze_air/apis/evidences.py +216 -0
- binalyze_air/apis/interact.py +36 -0
- binalyze_air/apis/params.py +40 -0
- binalyze_air/apis/settings.py +27 -0
- binalyze_air/apis/user_management.py +74 -0
- binalyze_air/apis/users.py +68 -0
- binalyze_air/apis/webhooks.py +231 -0
- binalyze_air/base.py +133 -0
- binalyze_air/client.py +1338 -0
- binalyze_air/commands/__init__.py +146 -0
- binalyze_air/commands/acquisitions.py +387 -0
- binalyze_air/commands/assets.py +363 -0
- binalyze_air/commands/authentication.py +37 -0
- binalyze_air/commands/auto_asset_tags.py +231 -0
- binalyze_air/commands/baseline.py +396 -0
- binalyze_air/commands/cases.py +603 -0
- binalyze_air/commands/event_subscription.py +102 -0
- binalyze_air/commands/evidences.py +988 -0
- binalyze_air/commands/interact.py +58 -0
- binalyze_air/commands/organizations.py +221 -0
- binalyze_air/commands/policies.py +203 -0
- binalyze_air/commands/settings.py +29 -0
- binalyze_air/commands/tasks.py +56 -0
- binalyze_air/commands/triage.py +360 -0
- binalyze_air/commands/user_management.py +126 -0
- binalyze_air/commands/users.py +101 -0
- binalyze_air/config.py +245 -0
- binalyze_air/exceptions.py +50 -0
- binalyze_air/http_client.py +306 -0
- binalyze_air/models/__init__.py +285 -0
- binalyze_air/models/acquisitions.py +251 -0
- binalyze_air/models/assets.py +439 -0
- binalyze_air/models/audit.py +273 -0
- binalyze_air/models/authentication.py +70 -0
- binalyze_air/models/auto_asset_tags.py +117 -0
- binalyze_air/models/baseline.py +232 -0
- binalyze_air/models/cases.py +276 -0
- binalyze_air/models/endpoints.py +76 -0
- binalyze_air/models/event_subscription.py +172 -0
- binalyze_air/models/evidence.py +66 -0
- binalyze_air/models/evidences.py +349 -0
- binalyze_air/models/interact.py +136 -0
- binalyze_air/models/organizations.py +294 -0
- binalyze_air/models/params.py +128 -0
- binalyze_air/models/policies.py +250 -0
- binalyze_air/models/settings.py +84 -0
- binalyze_air/models/tasks.py +149 -0
- binalyze_air/models/triage.py +143 -0
- binalyze_air/models/user_management.py +97 -0
- binalyze_air/models/users.py +82 -0
- binalyze_air/queries/__init__.py +134 -0
- binalyze_air/queries/acquisitions.py +156 -0
- binalyze_air/queries/assets.py +105 -0
- binalyze_air/queries/audit.py +417 -0
- binalyze_air/queries/authentication.py +56 -0
- binalyze_air/queries/auto_asset_tags.py +60 -0
- binalyze_air/queries/baseline.py +185 -0
- binalyze_air/queries/cases.py +293 -0
- binalyze_air/queries/endpoints.py +25 -0
- binalyze_air/queries/event_subscription.py +55 -0
- binalyze_air/queries/evidence.py +140 -0
- binalyze_air/queries/evidences.py +280 -0
- binalyze_air/queries/interact.py +28 -0
- binalyze_air/queries/organizations.py +223 -0
- binalyze_air/queries/params.py +115 -0
- binalyze_air/queries/policies.py +150 -0
- binalyze_air/queries/settings.py +20 -0
- binalyze_air/queries/tasks.py +82 -0
- binalyze_air/queries/triage.py +231 -0
- binalyze_air/queries/user_management.py +83 -0
- binalyze_air/queries/users.py +69 -0
- binalyze_air_sdk-1.0.1.dist-info/METADATA +635 -0
- binalyze_air_sdk-1.0.1.dist-info/RECORD +82 -0
- binalyze_air_sdk-1.0.1.dist-info/WHEEL +5 -0
- 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")
|