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,27 @@
1
+ """
2
+ Settings API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from ..http_client import HTTPClient
6
+ from ..models.settings import BannerSettings, UpdateBannerSettingsRequest
7
+ from ..queries.settings import GetBannerSettingsQuery
8
+ from ..commands.settings import UpdateBannerSettingsCommand
9
+
10
+
11
+ class SettingsAPI:
12
+ """Settings API with CQRS pattern - separated queries and commands."""
13
+
14
+ def __init__(self, http_client: HTTPClient):
15
+ self.http_client = http_client
16
+
17
+ # QUERIES (Read operations)
18
+ def get_banner_settings(self) -> BannerSettings:
19
+ """Get current banner settings."""
20
+ query = GetBannerSettingsQuery(self.http_client)
21
+ return query.execute()
22
+
23
+ # COMMANDS (Write operations)
24
+ def update_banner_settings(self, request: UpdateBannerSettingsRequest) -> BannerSettings:
25
+ """Update banner settings."""
26
+ command = UpdateBannerSettingsCommand(self.http_client, request)
27
+ return command.execute()
@@ -0,0 +1,74 @@
1
+ """
2
+ User Management API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from ..http_client import HTTPClient
8
+ from ..models.user_management import (
9
+ UserManagementUser, UserFilter, CreateUserRequest, UpdateUserRequest,
10
+ AIUser, CreateAIUserRequest, APIUser, CreateAPIUserRequest
11
+ )
12
+ from ..queries.user_management import (
13
+ ListUsersQuery, GetUserQuery, GetAIUserQuery, GetAPIUserQuery
14
+ )
15
+ from ..commands.user_management import (
16
+ CreateUserCommand, UpdateUserCommand, DeleteUserCommand,
17
+ CreateAIUserCommand, CreateAPIUserCommand
18
+ )
19
+
20
+
21
+ class UserManagementAPI:
22
+ """User Management API with CQRS pattern - separated queries and commands."""
23
+
24
+ def __init__(self, http_client: HTTPClient):
25
+ self.http_client = http_client
26
+
27
+ # USER QUERIES (Read operations)
28
+ def list_users(self, filter_params: Optional[UserFilter] = None) -> List[UserManagementUser]:
29
+ """List users with optional filtering."""
30
+ query = ListUsersQuery(self.http_client, filter_params)
31
+ return query.execute()
32
+
33
+ def get_user(self, user_id: str) -> UserManagementUser:
34
+ """Get a specific user by ID."""
35
+ query = GetUserQuery(self.http_client, user_id)
36
+ return query.execute()
37
+
38
+ # USER COMMANDS (Write operations)
39
+ def create_user(self, request: CreateUserRequest) -> UserManagementUser:
40
+ """Create a new user."""
41
+ command = CreateUserCommand(self.http_client, request)
42
+ return command.execute()
43
+
44
+ def update_user(self, user_id: str, request: UpdateUserRequest) -> UserManagementUser:
45
+ """Update an existing user."""
46
+ command = UpdateUserCommand(self.http_client, user_id, request)
47
+ return command.execute()
48
+
49
+ def delete_user(self, user_id: str) -> Dict[str, Any]:
50
+ """Delete a user."""
51
+ command = DeleteUserCommand(self.http_client, user_id)
52
+ return command.execute()
53
+
54
+ # AI USER OPERATIONS
55
+ def get_ai_user(self) -> AIUser:
56
+ """Get the AI user."""
57
+ query = GetAIUserQuery(self.http_client)
58
+ return query.execute()
59
+
60
+ def create_ai_user(self, request: CreateAIUserRequest) -> AIUser:
61
+ """Create a new AI user."""
62
+ command = CreateAIUserCommand(self.http_client, request)
63
+ return command.execute()
64
+
65
+ # API USER OPERATIONS
66
+ def get_api_user(self) -> APIUser:
67
+ """Get the API user."""
68
+ query = GetAPIUserQuery(self.http_client)
69
+ return query.execute()
70
+
71
+ def create_api_user(self, request: CreateAPIUserRequest) -> APIUser:
72
+ """Create a new API user."""
73
+ command = CreateAPIUserCommand(self.http_client, request)
74
+ return command.execute()
@@ -0,0 +1,68 @@
1
+ """
2
+ Users API for the Binalyze AIR SDK.
3
+ """
4
+
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from ..http_client import HTTPClient
8
+ from ..models.users import (
9
+ User, UserFilter, CreateUserRequest, UpdateUserRequest,
10
+ APIUser, CreateAPIUserRequest
11
+ )
12
+ from ..queries.users import (
13
+ ListUsersQuery, GetUserQuery
14
+ )
15
+ from ..commands.users import (
16
+ CreateUserCommand, UpdateUserCommand, DeleteUserCommand,
17
+ CreateAPIUserCommand
18
+ )
19
+
20
+
21
+ class UsersAPI:
22
+ """Users API with CQRS pattern - separated queries and commands."""
23
+
24
+ def __init__(self, http_client: HTTPClient):
25
+ self.http_client = http_client
26
+
27
+ # USER QUERIES (Read operations)
28
+ def list(self, page_number: int = 1, page_size: int = 10,
29
+ sort_by: str = "createdAt", sort_type: str = "ASC") -> List[User]:
30
+ """List users with pagination support."""
31
+ query = ListUsersQuery(self.http_client, page_number, page_size, sort_by, sort_type)
32
+ return query.execute()
33
+
34
+ def get(self, user_id: str) -> User:
35
+ """Get a specific user by ID."""
36
+ query = GetUserQuery(self.http_client, user_id)
37
+ return query.execute()
38
+
39
+ # USER COMMANDS (Write operations)
40
+ def create_user(self, request: CreateUserRequest) -> User:
41
+ """Create a new user."""
42
+ command = CreateUserCommand(self.http_client, request)
43
+ return command.execute()
44
+
45
+ def update_user(self, user_id: str, request: UpdateUserRequest) -> User:
46
+ """Update an existing user."""
47
+ command = UpdateUserCommand(self.http_client, user_id, request)
48
+ return command.execute()
49
+
50
+ def delete_user(self, user_id: str) -> Dict[str, Any]:
51
+ """Delete a user."""
52
+ command = DeleteUserCommand(self.http_client, user_id)
53
+ return command.execute()
54
+
55
+ # API USER OPERATIONS
56
+ def create_api_user(self, username: str, email: str, organization_ids: List[int],
57
+ profile: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
58
+ """Create a new API user for programmatic access."""
59
+ request_data = {
60
+ "username": username,
61
+ "email": email,
62
+ "organizationIds": organization_ids
63
+ }
64
+ if profile:
65
+ request_data["profile"] = profile
66
+
67
+ command = CreateAPIUserCommand(self.http_client, request_data)
68
+ return command.execute()
@@ -0,0 +1,231 @@
1
+ """
2
+ Webhook API for the Binalyze AIR SDK.
3
+ Provides webhook trigger functionality for programmatically calling webhook endpoints.
4
+ """
5
+
6
+ from typing import Dict, Any, Optional, List, Union
7
+ import json
8
+
9
+ from ..http_client import HTTPClient
10
+
11
+
12
+ class WebhookAPI:
13
+ """Webhook API for triggering webhook endpoints programmatically."""
14
+
15
+ def __init__(self, http_client: HTTPClient):
16
+ self.http_client = http_client
17
+
18
+ def trigger_get(
19
+ self,
20
+ slug: str,
21
+ data: str,
22
+ token: str,
23
+ use_webhook_endpoint: bool = True
24
+ ) -> Dict[str, Any]:
25
+ """
26
+ Trigger a webhook via GET request.
27
+
28
+ Args:
29
+ slug: Webhook slug/name
30
+ data: Comma-separated hostnames or IP addresses
31
+ token: Webhook token
32
+ use_webhook_endpoint: If True, use webhook endpoint directly (no auth needed)
33
+ If False, use authenticated API endpoint
34
+
35
+ Returns:
36
+ Dict containing task details and URLs
37
+ """
38
+ if use_webhook_endpoint:
39
+ # Direct webhook call - no authentication needed, just token
40
+ endpoint = f"webhook/{slug}/{data}"
41
+ params = {"token": token}
42
+
43
+ # Use raw HTTP client for webhook endpoints (they don't use standard API auth)
44
+ import requests
45
+ base_url = self.http_client.config.host
46
+ url = f"{base_url}/api/{endpoint}"
47
+
48
+ response = requests.get(
49
+ url,
50
+ params=params,
51
+ verify=self.http_client.config.verify_ssl,
52
+ timeout=30
53
+ )
54
+
55
+ if response.status_code == 200:
56
+ return response.json()
57
+ elif response.status_code == 403:
58
+ return {
59
+ "success": False,
60
+ "error": "Forbidden",
61
+ "message": "Invalid webhook token",
62
+ "statusCode": 403
63
+ }
64
+ elif response.status_code == 404:
65
+ return {
66
+ "success": False,
67
+ "error": "Not Found",
68
+ "message": "Webhook not found",
69
+ "statusCode": 404
70
+ }
71
+ else:
72
+ return {
73
+ "success": False,
74
+ "error": f"HTTP {response.status_code}",
75
+ "message": response.text,
76
+ "statusCode": response.status_code
77
+ }
78
+ else:
79
+ # Use authenticated API endpoint (if available)
80
+ endpoint = f"webhook/{slug}/{data}"
81
+ params = {"token": token}
82
+ return self.http_client.get(endpoint, params=params)
83
+
84
+ def trigger_post(
85
+ self,
86
+ slug: str,
87
+ token: str,
88
+ payload: Optional[Dict[str, Any]] = None,
89
+ use_webhook_endpoint: bool = True
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Trigger a webhook via POST request.
93
+
94
+ Args:
95
+ slug: Webhook slug/name
96
+ token: Webhook token
97
+ payload: Optional POST data/payload
98
+ use_webhook_endpoint: If True, use webhook endpoint directly (no auth needed)
99
+ If False, use authenticated API endpoint
100
+
101
+ Returns:
102
+ Dict containing task details and URLs
103
+ """
104
+ if payload is None:
105
+ payload = {}
106
+
107
+ if use_webhook_endpoint:
108
+ # Direct webhook call - no authentication needed, just token
109
+ endpoint = f"webhook/{slug}"
110
+ params = {"token": token}
111
+
112
+ # Use raw HTTP client for webhook endpoints
113
+ import requests
114
+ base_url = self.http_client.config.host
115
+ url = f"{base_url}/api/{endpoint}"
116
+
117
+ response = requests.post(
118
+ url,
119
+ params=params,
120
+ json=payload,
121
+ verify=self.http_client.config.verify_ssl,
122
+ timeout=30
123
+ )
124
+
125
+ if response.status_code == 200:
126
+ return response.json()
127
+ elif response.status_code == 403:
128
+ return {
129
+ "success": False,
130
+ "error": "Forbidden",
131
+ "message": "Invalid webhook token",
132
+ "statusCode": 403
133
+ }
134
+ elif response.status_code == 404:
135
+ return {
136
+ "success": False,
137
+ "error": "Not Found",
138
+ "message": "Webhook not found",
139
+ "statusCode": 404
140
+ }
141
+ else:
142
+ return {
143
+ "success": False,
144
+ "error": f"HTTP {response.status_code}",
145
+ "message": response.text,
146
+ "statusCode": response.status_code
147
+ }
148
+ else:
149
+ # Use authenticated API endpoint (if available)
150
+ endpoint = f"webhook/{slug}"
151
+ params = {"token": token}
152
+ return self.http_client.post(endpoint, json_data=payload, params=params)
153
+
154
+ def get_task_details(
155
+ self,
156
+ slug: str,
157
+ token: str,
158
+ task_id: str,
159
+ use_webhook_endpoint: bool = True
160
+ ) -> Union[List[Dict[str, Any]], Dict[str, Any]]:
161
+ """
162
+ Get task assignment details from a webhook.
163
+
164
+ Args:
165
+ slug: Webhook slug/name
166
+ token: Webhook token
167
+ task_id: Task ID returned from webhook trigger
168
+ use_webhook_endpoint: If True, use webhook endpoint directly (no auth needed)
169
+ If False, use authenticated API endpoint
170
+
171
+ Returns:
172
+ List of task assignment details or Dict with error info
173
+ """
174
+ if use_webhook_endpoint:
175
+ # Direct webhook call - no authentication needed, just token
176
+ endpoint = f"webhook/{slug}/assignments"
177
+ params = {"token": token, "taskId": task_id}
178
+
179
+ # Use raw HTTP client for webhook endpoints
180
+ import requests
181
+ base_url = self.http_client.config.host
182
+ url = f"{base_url}/api/{endpoint}"
183
+
184
+ response = requests.get(
185
+ url,
186
+ params=params,
187
+ verify=self.http_client.config.verify_ssl,
188
+ timeout=30
189
+ )
190
+
191
+ if response.status_code == 200:
192
+ return response.json()
193
+ elif response.status_code == 403:
194
+ return {
195
+ "success": False,
196
+ "error": "Forbidden",
197
+ "message": "Invalid webhook token",
198
+ "statusCode": 403
199
+ }
200
+ elif response.status_code == 404:
201
+ return {
202
+ "success": False,
203
+ "error": "Not Found",
204
+ "message": "Task not found",
205
+ "statusCode": 404
206
+ }
207
+ else:
208
+ return {
209
+ "success": False,
210
+ "error": f"HTTP {response.status_code}",
211
+ "message": response.text,
212
+ "statusCode": response.status_code
213
+ }
214
+ else:
215
+ # Use authenticated API endpoint (if available)
216
+ endpoint = f"webhook/{slug}/assignments"
217
+ params = {"token": token, "taskId": task_id}
218
+ return self.http_client.get(endpoint, params=params)
219
+
220
+ # Convenience methods with better names
221
+ def call_webhook_get(self, slug: str, data: str, token: str) -> Dict[str, Any]:
222
+ """Convenience method to call a webhook via GET."""
223
+ return self.trigger_get(slug, data, token)
224
+
225
+ def call_webhook_post(self, slug: str, token: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
226
+ """Convenience method to call a webhook via POST."""
227
+ return self.trigger_post(slug, token, payload)
228
+
229
+ def get_webhook_task_data(self, slug: str, token: str, task_id: str) -> Union[List[Dict[str, Any]], Dict[str, Any]]:
230
+ """Convenience method to get webhook task assignment data."""
231
+ return self.get_task_details(slug, token, task_id)
binalyze_air/base.py ADDED
@@ -0,0 +1,133 @@
1
+ """
2
+ Base classes for CQRS implementation in the AIR SDK.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, List, Optional, TypeVar, Generic
7
+ from pydantic import BaseModel as PydanticBaseModel, ConfigDict
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ class Query(Generic[T], ABC):
13
+ """Base class for all queries (read operations)."""
14
+
15
+ @abstractmethod
16
+ def execute(self) -> T:
17
+ """Execute the query and return the result."""
18
+ pass
19
+
20
+
21
+ class Command(Generic[T], ABC):
22
+ """Base class for all commands (write operations)."""
23
+
24
+ @abstractmethod
25
+ def execute(self) -> T:
26
+ """Execute the command and return the result."""
27
+ pass
28
+
29
+
30
+ class AIRBaseModel(PydanticBaseModel):
31
+ """Base Pydantic model with common configurations."""
32
+
33
+ model_config = ConfigDict(
34
+ use_enum_values=True,
35
+ validate_assignment=True,
36
+ arbitrary_types_allowed=True
37
+ )
38
+
39
+
40
+ class PaginatedResponse(AIRBaseModel, Generic[T]):
41
+ """Generic paginated response model."""
42
+
43
+ entities: List[T]
44
+ total_entity_count: int
45
+ current_page: int
46
+ page_size: int
47
+ total_page_count: int
48
+ previous_page: Optional[int] = None
49
+ next_page: Optional[int] = None
50
+
51
+
52
+ class APIResponse(AIRBaseModel, Generic[T]):
53
+ """Generic API response model."""
54
+
55
+ success: bool
56
+ result: T
57
+ status_code: int
58
+ errors: List[str] = []
59
+
60
+
61
+ class Filter(AIRBaseModel):
62
+ """Base filter model for queries with pagination and sorting support."""
63
+
64
+ # Basic filter fields
65
+ search_term: Optional[str] = None
66
+ organization_ids: Optional[List[int]] = None
67
+
68
+ # Pagination parameters (match API documentation exactly) - no defaults
69
+ page_number: Optional[int] = None
70
+ page_size: Optional[int] = None
71
+
72
+ # Sorting parameters (match API documentation exactly) - no defaults
73
+ sort_by: Optional[str] = None
74
+ sort_type: Optional[str] = None
75
+
76
+ def to_params(self) -> Dict[str, Any]:
77
+ """Convert filter to API parameters."""
78
+ params = {}
79
+
80
+ # Pagination parameters (not in filter namespace) - only if set
81
+ if self.page_number is not None:
82
+ params["pageNumber"] = self.page_number
83
+ if self.page_size is not None:
84
+ params["pageSize"] = self.page_size
85
+ if self.sort_by is not None:
86
+ params["sortBy"] = self.sort_by
87
+ if self.sort_type is not None:
88
+ params["sortType"] = self.sort_type
89
+
90
+ # Filter parameters (in filter namespace)
91
+ for field_name, field_value in self.model_dump(exclude_none=True).items():
92
+ # Skip pagination/sorting fields as they're handled above
93
+ if field_name in ["page_number", "page_size", "sort_by", "sort_type"]:
94
+ continue
95
+
96
+ if field_value is not None:
97
+ if isinstance(field_value, list):
98
+ params[f"filter[{field_name}]"] = ",".join(map(str, field_value))
99
+ else:
100
+ params[f"filter[{field_name}]"] = str(field_value)
101
+ return params
102
+
103
+
104
+ class PaginatedList(list):
105
+ """List-like container that carries pagination metadata.
106
+ This allows SDK query methods to return a normal iterable while still
107
+ exposing additional pagination attributes such as total_entity_count.
108
+ The class simply subclasses ``list`` and adds attributes during
109
+ construction. All standard list operations remain intact.
110
+ """
111
+ def __init__(
112
+ self,
113
+ iterable=None,
114
+ *,
115
+ total_entity_count: int | None = None,
116
+ current_page: int | None = None,
117
+ page_size: int | None = None,
118
+ total_page_count: int | None = None,
119
+ ) -> None:
120
+ super().__init__(iterable or [])
121
+ # Store metadata for caller introspection
122
+ self.total_entity_count = total_entity_count
123
+ self.current_page = current_page
124
+ self.page_size = page_size
125
+ self.total_page_count = total_page_count
126
+
127
+ # Optional: pretty representation including meta for debugging
128
+ def __repr__(self) -> str: # noqa: D401
129
+ meta = (
130
+ f" total={self.total_entity_count} page={self.current_page} "
131
+ f"size={self.page_size} pages={self.total_page_count}"
132
+ )
133
+ return f"PaginatedList({list.__repr__(self)},{meta})"