mantis_api_client 5.5.0__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.
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ from typing import Any
4
+
5
+ import requests
6
+
7
+ from mantis_api_client.config import mantis_api_client_config
8
+ from mantis_api_client.oidc import authorize
9
+
10
+ # -------------------------------------------------------------------------- #
11
+ # Internal helpers
12
+ # -------------------------------------------------------------------------- #
13
+
14
+
15
+ def _get(route: str, **kwargs: Any) -> requests.Response:
16
+ return requests.get(
17
+ f"{mantis_api_client_config.user_api_url}{route}",
18
+ verify=mantis_api_client_config.cacert,
19
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
20
+ **kwargs,
21
+ )
22
+
23
+
24
+ def _post(route: str, **kwargs: Any) -> requests.Response:
25
+ return requests.post(
26
+ f"{mantis_api_client_config.user_api_url}{route}",
27
+ verify=mantis_api_client_config.cacert,
28
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
29
+ **kwargs,
30
+ )
31
+
32
+
33
+ def _put(route: str, **kwargs: Any) -> requests.Response:
34
+ return requests.put(
35
+ f"{mantis_api_client_config.user_api_url}{route}",
36
+ verify=mantis_api_client_config.cacert,
37
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
38
+ **kwargs,
39
+ )
40
+
41
+
42
+ def _delete(route: str, **kwargs: Any) -> requests.Response:
43
+ return requests.delete(
44
+ f"{mantis_api_client_config.user_api_url}{route}",
45
+ verify=mantis_api_client_config.cacert,
46
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
47
+ **kwargs,
48
+ )
49
+
50
+
51
+ def _handle_error(
52
+ result: requests.Response, context_error_msg: str
53
+ ) -> requests.Response:
54
+ error_msg = ""
55
+ if result.headers.get("content-type") == "application/json":
56
+ error_data = result.json()
57
+ for k in ("message", "detail"):
58
+ if k in error_data:
59
+ error_msg = error_data[k]
60
+ break
61
+ if not error_msg:
62
+ error_msg = result.text
63
+
64
+ raise Exception(
65
+ f"{context_error_msg}. "
66
+ f"Status code: '{result.status_code}'.\n"
67
+ f"Error message: '{error_msg}'."
68
+ )
69
+
70
+
71
+ # -------------------------------------------------------------------------- #
72
+ # Internal helpers
73
+ # -------------------------------------------------------------------------- #
74
+
75
+
76
+ def get_version() -> str:
77
+ """
78
+ Return User API version.
79
+
80
+ :return: The version number is a string
81
+ """
82
+ result = authorize(_get)("/version")
83
+
84
+ if result.status_code != 200:
85
+ _handle_error(result, "Cannot retrieve User API version")
86
+
87
+ return result.json()
88
+
89
+
90
+ # -------------------------------------------------------------------------- #
91
+ # User API (users)
92
+ # -------------------------------------------------------------------------- #
93
+
94
+
95
+ # def fetch_users() -> Any:
96
+ # """Fetch users of the current organization."""
97
+ # result = authorize(_get)("/users")
98
+
99
+ # if result.status_code != 200:
100
+ # _handle_error(result, "Cannot retrieve user list from User API")
101
+ # else:
102
+ # data = result.json()
103
+
104
+ # return data
105
+
106
+
107
+ def fetch_current_user() -> Any:
108
+ """Fetch current user information."""
109
+ result = authorize(_get)("/my/self")
110
+
111
+ if result.status_code != 200:
112
+ _handle_error(result, "Cannot retrieve current user from User API")
113
+ else:
114
+ data = result.json()
115
+
116
+ return data
117
+
118
+
119
+ def fetch_user(user_id: str) -> Any:
120
+ """Fetch information for a specific user."""
121
+ result = authorize(_get)(f"/users/{user_id}")
122
+
123
+ if result.status_code != 200:
124
+ _handle_error(
125
+ result,
126
+ f"Cannot retrieve user info for user_id '{user_id}' from User API",
127
+ )
128
+ else:
129
+ data = result.json()
130
+
131
+ return data
132
+
133
+
134
+ # -------------------------------------------------------------------------- #
135
+ # User API (seats)
136
+ # -------------------------------------------------------------------------- #
137
+
138
+
139
+ def fetch_current_seats() -> Any:
140
+ """Fetch seats of the current user."""
141
+ result = authorize(_get)("/my/organizations")
142
+
143
+ if result.status_code != 200:
144
+ _handle_error(result, "Cannot retrieve current seat from User API")
145
+ else:
146
+ data = result.json()
147
+
148
+ return data
149
+
150
+
151
+ def fetch_current_workspaces() -> list:
152
+ """Fetch workspaces of the current user."""
153
+ result = authorize(_get)("/my/workspaces")
154
+
155
+ if result.status_code != 200:
156
+ _handle_error(result, "Cannot retrieve current workspaces from User API")
157
+ else:
158
+ data = result.json()
159
+
160
+ return data
161
+
162
+
163
+ # -------------------------------------------------------------------------- #
164
+ # User API (organizations)
165
+ # -------------------------------------------------------------------------- #
166
+
167
+
168
+ def fetch_organization(organization_id: str) -> Any:
169
+ """Fetch information for a specific organization."""
170
+ result = authorize(_get)(f"/organizations/{organization_id}")
171
+
172
+ if result.status_code != 200:
173
+ _handle_error(
174
+ result,
175
+ f"Cannot retrieve organization for organization_id {organization_id} from User API",
176
+ )
177
+ else:
178
+ data = result.json()
179
+
180
+ return data
181
+
182
+
183
+ def get_organization_workspaces(organization_id: str) -> list[dict]:
184
+ """Retrieve workspaces from an organization ID."""
185
+
186
+ result = authorize(_get)(f"/organization/{organization_id}/workspaces")
187
+
188
+ if result.status_code != 200:
189
+ _handle_error(
190
+ result,
191
+ f"Cannot retrieve workspaces from organization ID '{organization_id}' from User API",
192
+ )
193
+ else:
194
+ data = result.json()
195
+
196
+ return data
197
+
198
+
199
+ def get_plan(plan_id: str) -> dict:
200
+ """Retrieve plan information for a specific plan ID."""
201
+
202
+ result = authorize(_get)(f"/plans/{plan_id}")
203
+
204
+ if result.status_code != 200:
205
+ _handle_error(
206
+ result,
207
+ f"Cannot retrieve plan info for plan ID '{plan_id}' from User API",
208
+ )
209
+ else:
210
+ data = result.json()
211
+
212
+ return data
213
+
214
+
215
+ def get_plan_limits(plan_id: str) -> dict:
216
+ """Retrieve plan limits for a specific plan ID."""
217
+
218
+ result = authorize(_get)(f"/plans/{plan_id}/limits")
219
+
220
+ if result.status_code != 200:
221
+ _handle_error(
222
+ result,
223
+ f"Cannot retrieve plan limits for plan ID '{plan_id}' from User API",
224
+ )
225
+ else:
226
+ data = result.json()
227
+
228
+ return data
229
+
230
+
231
+ def get_credits_list(organization_id: str) -> dict:
232
+ """Retrieve credits list for a specific organization ID."""
233
+
234
+ result = authorize(_get)(
235
+ "/credits",
236
+ params={"organization_id": organization_id},
237
+ )
238
+
239
+ if result.status_code != 200:
240
+ _handle_error(
241
+ result,
242
+ f"Cannot retrieve credits list for organization ID '{organization_id}' from User API",
243
+ )
244
+ else:
245
+ data = result.json()
246
+
247
+ return data
248
+
249
+
250
+ def get_consumption_history(organization_id: str) -> dict:
251
+ """Retrieve consumption history for a specific organization ID."""
252
+
253
+ result = authorize(_get)(
254
+ "/history",
255
+ params={"organization_id": organization_id},
256
+ )
257
+
258
+ if result.status_code != 200:
259
+ _handle_error(
260
+ result,
261
+ f"Cannot retrieve consumption history for organization ID '{organization_id}' from User API",
262
+ )
263
+ else:
264
+ data = result.json()
265
+
266
+ return data
267
+
268
+
269
+ def get_subject_roles() -> dict[str, list[dict]]:
270
+ """Retrieve subject roles."""
271
+
272
+ result = authorize(_get)("/my/roles")
273
+
274
+ if result.status_code != "200":
275
+ _handle_error(
276
+ result,
277
+ "Cannot retrieve subject roles from User API",
278
+ )
279
+ else:
280
+ data = result.json()
281
+
282
+ return data
@@ -0,0 +1,130 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import time
4
+ from datetime import datetime
5
+ from typing import Dict
6
+ from typing import List
7
+ from typing import Optional
8
+
9
+ from alive_progress import alive_bar
10
+ from mantis_scenario_model.lab_model import ScenarioExecutionStatus
11
+ from urllib3.exceptions import NewConnectionError
12
+
13
+ from mantis_api_client import scenario_api
14
+
15
+ try:
16
+ from colorama import init
17
+ from termcolor import colored
18
+
19
+ HAS_COLOUR = True
20
+ except ImportError:
21
+ HAS_COLOUR = False
22
+
23
+ # Initialize colorama
24
+ if HAS_COLOUR:
25
+ init(autoreset=True)
26
+ else:
27
+ # Override colored function to return first argument
28
+ def colored(text: object, *args: Any, **kwargs: Any) -> str: # type: ignore # noqa
29
+ return str(text)
30
+
31
+
32
+ def wait_lab(
33
+ lab_id: str,
34
+ quiet: bool = False,
35
+ exit_on_status: Optional[List[ScenarioExecutionStatus]] = None,
36
+ ) -> None:
37
+ """
38
+ - exit_on_status: allow to break the wait loop if current status matches one of the
39
+ """
40
+
41
+ if exit_on_status is None:
42
+ exit_on_status = []
43
+
44
+ notifications_output: List[Dict[str, str]] = []
45
+
46
+ if not quiet:
47
+ print("[+] Notifications:")
48
+
49
+ with alive_bar(
50
+ refresh_secs=0.08,
51
+ spinner=None,
52
+ enrich_print=False,
53
+ stats=False,
54
+ monitor=False,
55
+ theme="classic",
56
+ ) as bar:
57
+ nb_requests_failed = 0
58
+ while True:
59
+ in_error = False
60
+
61
+ time.sleep(1)
62
+
63
+ # Retrieve current lab status and associated
64
+ # notifications. Only display error message if it occurs
65
+ # multiple times.
66
+ try:
67
+ lab = scenario_api.fetch_lab(lab_id)
68
+ except NewConnectionError as e:
69
+ in_error = True
70
+ nb_requests_failed += 1
71
+ if nb_requests_failed > 10:
72
+ print(e)
73
+ except Exception as e:
74
+ in_error = True
75
+ nb_requests_failed += 1
76
+ if nb_requests_failed > 10:
77
+ print(e)
78
+
79
+ # Exit in case of multiple failures when fetching lab status
80
+ if nb_requests_failed > 10:
81
+ break
82
+
83
+ if in_error:
84
+ continue
85
+
86
+ if not quiet:
87
+ # Retrieve lab notifications
88
+ notifications = scenario_api.fetch_lab_notifications(lab_id)
89
+
90
+ # Only display new notifications
91
+ new_output = notifications[len(notifications_output) :]
92
+ for event in new_output:
93
+ event = json.loads(event)
94
+ data = event["event_data"]
95
+ iso_str = event["event_datetime"]
96
+ dtime = datetime.fromisoformat(iso_str).strftime(
97
+ "%Y-%m-%d %H:%M:%S"
98
+ )
99
+ print(f"⚡ {dtime} UTC - {data}")
100
+ notifications_output = notifications
101
+
102
+ # Display current macro step (deploy, provisioning, attack, etc.)
103
+ if "status" in lab:
104
+ bar.text(lab["status"])
105
+ if lab["status"].rstrip() == "PAUSE":
106
+ with bar.pause():
107
+ print("Press Enter to continue the scenario...")
108
+ input()
109
+ scenario_api.resume_lab(lab_id)
110
+ lab = scenario_api.fetch_lab(lab_id)
111
+ else:
112
+ bar()
113
+
114
+ # Condition to break loop: the scenario execution
115
+ # has finished, either because it has been cancelled or
116
+ # because it has successfully finished
117
+ if "status" in lab and lab["status"] in [
118
+ ScenarioExecutionStatus.scenario_finished.value,
119
+ ScenarioExecutionStatus.completed.value,
120
+ ScenarioExecutionStatus.cancelled.value,
121
+ ScenarioExecutionStatus.error.value,
122
+ ]:
123
+ break
124
+
125
+ # Exit on specific statuses decided by the current function caller
126
+ if "status" in lab and lab["status"] in [s.value for s in exit_on_status]:
127
+ break
128
+
129
+ if not quiet:
130
+ print("[+] Lab content has been completed")
@@ -0,0 +1 @@
1
+ Created by Amossys <contact@amossys.fr>
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2016-2021 AMOSSYS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.1
2
+ Name: mantis_api_client
3
+ Version: 5.5.0
4
+ Summary: M&NTIS Platform client API
5
+ License: MIT
6
+ Author: AMOSSYS
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: alive-progress (>=3.1.5,<4.0.0)
15
+ Requires-Dist: argcomplete (>=1.12.1,<1.13.0)
16
+ Requires-Dist: colorama (>=0.4.4,<0.5.0)
17
+ Requires-Dist: cr-api-client (>=5.0.26,<6.0.0)
18
+ Requires-Dist: mantis-authz (>=0.2.3,<1.0.0)
19
+ Requires-Dist: mantis-catalog (>=0.1.2,<0.2.0)
20
+ Requires-Dist: mantis-logger (>=0.2.0,<0.3.0)
21
+ Requires-Dist: mantis-models (>=3.4.11,<4.0.0)
22
+ Requires-Dist: omegaconf (>=2.2.2,<3.0.0)
23
+ Requires-Dist: pydantic (>=2.10.3,<3.0.0)
24
+ Requires-Dist: requests (>=2.24.0,<3.0.0)
25
+ Requires-Dist: rich (>=13.7.1,<14.0.0)
26
+ Requires-Dist: rich_argparse (>=1.5.2,<2.0.0)
27
+ Requires-Dist: ruamel-yaml (>=0.16.10,<0.17.0)
28
+ Requires-Dist: ruamel.yaml (>=0.16.10,<0.17.0)
29
+ Requires-Dist: termcolor (>=1.1.0,<1.2.0)
30
+ Description-Content-Type: text/markdown
31
+
32
+ # M&NTIS Platform client API
33
+
@@ -0,0 +1,28 @@
1
+ mantis_api_client/__init__.py,sha256=a0qcQBVKkpnLmwNU6jpqAPYR0_9rDCg-8seK-9uu1AY,581
2
+ mantis_api_client/cli_parser/account_parser.py,sha256=wUosGQ8Jadnjd_526kLRgxVIJZx4MULO4fbLBpnOTJk,15369
3
+ mantis_api_client/cli_parser/attack_parser.py,sha256=ol0biecEO0yOx6RvYWoHTBCf3LMa9wrBVYQjlSkhmQo,10143
4
+ mantis_api_client/cli_parser/bas_parser.py,sha256=T7ARupf5-KaE7R-CsBtgf2Y15rkv2aR1g47wpm1wfHQ,39484
5
+ mantis_api_client/cli_parser/basebox_parser.py,sha256=56ZvZe96TZm44wHd9lCmkOq_Dvi7-PmsTUYl1e8HtnY,8267
6
+ mantis_api_client/cli_parser/dataset_parser.py,sha256=T4e7lN6Qh6FryKtb-DzNj0dO-jiC5sgMBxVT3b1I8G0,6469
7
+ mantis_api_client/cli_parser/lab_parser.py,sha256=5lV5ESqtj0yz4PUb85O0ANx2z7JbzAYAhMKMYxvUJTU,24542
8
+ mantis_api_client/cli_parser/labs_parser.py,sha256=HcC_gCCd7okHU6pjZaHZOhU7vpfBjXQub6WQeL4AOeE,3057
9
+ mantis_api_client/cli_parser/log_collector_parser.py,sha256=MtWJLiI_64B306qgLIs9oPwywYcF_ndulykyMtdvTy0,2316
10
+ mantis_api_client/cli_parser/redteam_parser.py,sha256=go0dBgWVrRnqOyJ0sfaX5FfqSlQfq-qvNl-nAOifUJE,11187
11
+ mantis_api_client/cli_parser/scenario_parser.py,sha256=axlTdcwWp3x9N_ZvhWfPOD9W42gmnhct1l9_2lQzfA4,9918
12
+ mantis_api_client/cli_parser/signature_parser.py,sha256=XIRu4MSfVbrmS2-gMP0Vi63Fp0HTX5z4yHcgs0gmhqg,4774
13
+ mantis_api_client/cli_parser/topology_parser.py,sha256=Opvk02bG_8slkCWNSf9hLofklXcDH9w4G5D8Wz9Uke8,7919
14
+ mantis_api_client/cli_parser/user_parser.py,sha256=oa414THhg8HWetDYCMs3_XHTKBPpe3UnH5bTBWZ066Y,7256
15
+ mantis_api_client/config.py,sha256=eCXId_C0Xe-KAey8irnN4Dk8FCGmppL1XqguuvLdn04,2588
16
+ mantis_api_client/dataset_api.py,sha256=n30NjHOc7OnbX_p1sFAFgvKCbTYHoFR0sxIkmeVYaNc,9008
17
+ mantis_api_client/exceptions.py,sha256=EbfPig5Mu0aa8iqqILmOIjbfcFm3nfUhebeNYbHGe68,1187
18
+ mantis_api_client/mantis.py,sha256=f0LTOM8XdzXWePUPhZmn9sseLgly50GrrLOfTb5485w,5949
19
+ mantis_api_client/oidc.py,sha256=-H3-hlWbS7N44ZbgQvf6fkLFyzovaMTCaKQS5OCetYE,10756
20
+ mantis_api_client/scenario_api.py,sha256=es7btbpVkfIMnVmtNqSSLhuSwNEfxov6nfwoSYkrgTc,31306
21
+ mantis_api_client/user_api.py,sha256=IXJ3sOlWPgWJUhfV08qsWO7dIfUDdIbDTAGOjRx-gxY,7588
22
+ mantis_api_client/utils.py,sha256=d5TxLzofDcK4WbgTrm8mz5_M9Wvuq-ANgsjZIhbzQIU,4182
23
+ mantis_api_client-5.5.0.dist-info/AUTHORS,sha256=cZG0yas_cZYZStWPnFCG3wL9kNtihpB7rONDqqOhCk8,40
24
+ mantis_api_client-5.5.0.dist-info/LICENSE,sha256=8sBbPetGUTlhPzK-BXmdnV_hadvwFxL77L4WsghguvI,1056
25
+ mantis_api_client-5.5.0.dist-info/METADATA,sha256=pLixUQ828NKxK54fFXVo-Yehan_3HKexi4OLxSILgQQ,1242
26
+ mantis_api_client-5.5.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
27
+ mantis_api_client-5.5.0.dist-info/entry_points.txt,sha256=h3Qq03yHa0O9niMtTDGIj7hE2SqXbZBR3Bt7kkQSIJs,56
28
+ mantis_api_client-5.5.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.9.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ mantis=mantis_api_client.mantis:main
3
+