mantis_api_client 5.2.3__tar.gz → 5.3.0__tar.gz

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 (27) hide show
  1. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/PKG-INFO +3 -3
  2. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/account_parser.py +57 -28
  3. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/attack_parser.py +11 -16
  4. mantis_api_client-5.3.0/mantis_api_client/cli_parser/bas_parser.py +923 -0
  5. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/basebox_parser.py +15 -14
  6. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/dataset_parser.py +40 -30
  7. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/lab_parser.py +24 -4
  8. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/labs_parser.py +3 -3
  9. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/redteam_parser.py +66 -8
  10. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/scenario_parser.py +11 -16
  11. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/topology_parser.py +15 -14
  12. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/user_parser.py +10 -8
  13. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/dataset_api.py +3 -7
  14. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/mantis.py +13 -6
  15. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/oidc.py +12 -12
  16. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/scenario_api.py +87 -29
  17. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/user_api.py +45 -19
  18. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/utils.py +31 -10
  19. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/pyproject.toml +3 -3
  20. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/AUTHORS +0 -0
  21. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/LICENSE +0 -0
  22. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/README.md +0 -0
  23. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/__init__.py +0 -0
  24. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/log_collector_parser.py +0 -0
  25. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/cli_parser/signature_parser.py +0 -0
  26. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/config.py +0 -0
  27. {mantis_api_client-5.2.3 → mantis_api_client-5.3.0}/mantis_api_client/exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mantis_api_client
3
- Version: 5.2.3
3
+ Version: 5.3.0
4
4
  Summary: M&NTIS Platform client API
5
5
  License: MIT
6
6
  Author: AMOSSYS
@@ -14,11 +14,11 @@ Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: alive-progress (>=3.1.5,<4.0.0)
15
15
  Requires-Dist: argcomplete (>=1.12.1,<1.13.0)
16
16
  Requires-Dist: colorama (>=0.4.4,<0.5.0)
17
- Requires-Dist: cr-api-client (>=5.0.18,<6.0.0)
17
+ Requires-Dist: cr-api-client (>=5.0.22,<6.0.0)
18
18
  Requires-Dist: mantis-authz (>=0.2.3,<1.0.0)
19
19
  Requires-Dist: mantis-catalog (>=0.1.2,<0.2.0)
20
20
  Requires-Dist: mantis-logger (>=0.2.0,<0.3.0)
21
- Requires-Dist: mantis-models (>=3.2.2,<4.0.0)
21
+ Requires-Dist: mantis-models (>=3.4.0,<4.0.0)
22
22
  Requires-Dist: omegaconf (>=2.2.2,<3.0.0)
23
23
  Requires-Dist: pydantic (>=2.10.3,<3.0.0)
24
24
  Requires-Dist: requests (>=2.24.0,<3.0.0)
@@ -18,10 +18,12 @@ import requests
18
18
  from mantis_authz import jwt
19
19
  from rich.console import Console
20
20
  from rich.prompt import Prompt
21
+ from rich.tree import Tree
21
22
 
23
+ import mantis_api_client
22
24
  import mantis_api_client.dataset_api as dataset_api
23
- import mantis_api_client.oidc
24
25
  import mantis_api_client.scenario_api as scenario_api
26
+ from mantis_api_client import user_api
25
27
  from mantis_api_client.config import mantis_api_client_config
26
28
  from mantis_api_client.oidc import get_oidc_client
27
29
  from mantis_api_client.utils import colored
@@ -100,12 +102,14 @@ def status_handler(args: Any) -> None: # noqa: C901
100
102
  try:
101
103
  scenario_api_version = scenario_api.get_version()
102
104
  scenario_vers = Version(str_vers=scenario_api_version)
105
+ cyber_range_version = scenario_api.get_cyberrange_version()
103
106
  except requests.exceptions.ConnectionError:
104
107
  exit_code = 1
105
108
  print(" [-] API status: " + colored("not running !", "white", "on_red"))
106
109
  else:
107
110
  print(" [+] API status: " + colored("OK", "grey", "on_green"))
108
111
  print(" [+] version: {}".format(scenario_api_version))
112
+ print(" [+] Cyber Range version: {}".format(cyber_range_version))
109
113
  if scenario_vers.major != client_vers.major:
110
114
  exit_code = 1
111
115
  print(
@@ -144,7 +148,7 @@ def info_handler(args: Any) -> None: # noqa: C901
144
148
 
145
149
  # Handle case where the user pertains to an organization
146
150
  if user_groups is not None:
147
- default_group = get_oidc_client().get_default_group()
151
+ default_group = get_oidc_client().get_default_workspace()
148
152
  groups_comma_sep = ", ".join(user_groups)
149
153
  if default_group:
150
154
  # mark default group bold
@@ -166,6 +170,7 @@ def info_handler(args: Any) -> None: # noqa: C901
166
170
  # 'login_handler' handler
167
171
  #
168
172
  def login_handler(args: Any) -> None:
173
+ oidc_client = get_oidc_client()
169
174
  # Parameters
170
175
  oidc_domain = args.domain
171
176
  username = args.username
@@ -188,7 +193,6 @@ def login_handler(args: Any) -> None:
188
193
  "groups",
189
194
  "profile",
190
195
  "email",
191
- "dataset:read",
192
196
  "scenario:run",
193
197
  ]
194
198
  )
@@ -199,11 +203,11 @@ def login_handler(args: Any) -> None:
199
203
  thread = _init_create_callback_request_handler_thread(code_placeholder)
200
204
  thread.start()
201
205
  if username and password:
202
- token = get_oidc_client().token(
206
+ token = oidc_client.token(
203
207
  oidc_domain, username, password, redirect_uri=redirect_uri, scope=scope
204
208
  )
205
209
  else:
206
- auth_url = get_oidc_client().auth_url(
210
+ auth_url = oidc_client.auth_url(
207
211
  oidc_domain,
208
212
  redirect_uri=redirect_uri,
209
213
  scope=scope,
@@ -234,29 +238,54 @@ def login_handler(args: Any) -> None:
234
238
  redirect_uri=redirect_uri,
235
239
  )
236
240
  print("Access granted")
237
-
238
- claims = jwt.get_unverified_claims(token["access_token"])
239
- selected_group = args.group
240
- claimed_groups = claims.get("groups")
241
+ oidc_client.configure_profile(oidc_domain, token["refresh_token"])
241
242
 
242
243
  # Handle case where the user does not pertain to an organization
243
- if claimed_groups is None:
244
- selected_group = None
245
- else:
246
- # Handle case where the user pertains to only one organization
247
- if claimed_groups and len(claimed_groups) == 1 and selected_group is None:
248
- selected_group = claimed_groups[0]
249
-
250
- if selected_group not in claimed_groups:
251
- if selected_group:
252
- print(f"You are not member of group '{selected_group}'")
253
- selected_group = Prompt.ask(
254
- "Select a group to activate", choices=claimed_groups
255
- )
256
- print(f"Group `{selected_group}` activated")
244
+ subject_workspaces = user_api.fetch_current_workspaces()
245
+ subject_organizations = user_api.fetch_current_seats()
246
+ subject_orgs_wss = [
247
+ (sub_org, sub_ws)
248
+ for sub_org in subject_organizations
249
+ for sub_ws in subject_workspaces
250
+ if sub_ws["organization_id"] == sub_org["id"]
251
+ ]
252
+ selected_idx: int | None = None
253
+ if args.workspace:
254
+ for i, (_, ws) in enumerate(subject_orgs_wss):
255
+ if args.workspace == ws["id"]:
256
+ selected_idx = i
257
+ console = Console()
258
+ if selected_idx is None:
259
+ match len(subject_orgs_wss):
260
+ case 0:
261
+ return
262
+ case 1:
263
+ selected_idx = 0
264
+ case _:
265
+ i = 1
266
+ root = Tree(":file_folder:[yellow]Workspace memberships")
267
+ for org in subject_organizations:
268
+ org_branch = root.add(f"Organization [magenta]{org['name']}")
269
+ for ws in subject_workspaces:
270
+ if ws["organization_id"] != org["id"]:
271
+ continue
272
+ org_branch.add(rf"[bold green]{i}[/bold green]. {ws['name']}")
273
+ i += 1
274
+ console.print(root)
275
+ selected_idx = (
276
+ int(
277
+ Prompt.ask(
278
+ "Select a default workspace",
279
+ choices=[str(k + 1) for k in range(len(subject_orgs_wss))],
280
+ )
281
+ )
282
+ - 1
283
+ )
284
+ selected_ws = subject_orgs_wss[selected_idx][1]
285
+ console.print(f"Workspace [b green]{selected_ws['name']}[/b green] activated")
257
286
 
258
- get_oidc_client().configure_profile(
259
- oidc_domain, token["refresh_token"], selected_group
287
+ oidc_client.configure_profile(
288
+ oidc_domain, token["refresh_token"], selected_ws["id"]
260
289
  )
261
290
 
262
291
 
@@ -360,9 +389,9 @@ def add_account_parser(root_parser: argparse.ArgumentParser, subparsers: Any) ->
360
389
  help="Read your M&ntis cluster SSO password from a file",
361
390
  )
362
391
  parser_login.add_argument(
363
- "-g",
364
- "--group",
365
- help="Select the group name that will be used for contextual commands like `mantis scenario run`",
392
+ "-w",
393
+ "--workspace",
394
+ help="Pass the workspace that will be used as default for workspace-aware commands",
366
395
  )
367
396
  parser_login.set_defaults(func=login_handler)
368
397
 
@@ -11,7 +11,6 @@ from pydantic.json import pydantic_encoder
11
11
  from ruamel.yaml import YAML
12
12
 
13
13
  from mantis_api_client import scenario_api
14
- from mantis_api_client import user_api
15
14
  from mantis_api_client.oidc import get_oidc_client
16
15
  from mantis_api_client.utils import colored
17
16
  from mantis_api_client.utils import wait_lab
@@ -85,20 +84,19 @@ def _attack_create_or_run_lab(args: Any, do_run: bool = True):
85
84
  scenario_profile = args.scenario_profile
86
85
  lab_config_path = args.lab_config_path
87
86
 
88
- if not args.group_name:
87
+ if not args.workspace_id:
89
88
  try:
90
- group_name = get_oidc_client().get_default_group(raise_exc=True)
89
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
91
90
  except Exception as e:
92
91
  print(colored(f"Error when fetching attacks: '{e}'", "red"))
93
92
  sys.exit(1)
94
93
  else:
95
- group_name = args.group_name
94
+ workspace_id = args.workspace_id
96
95
 
97
96
  # Retrieve associated group id
98
- if group_name is None:
97
+ if workspace_id is None:
99
98
  print(colored("Your subscription level does not permit to launch labs", "red"))
100
99
  sys.exit(1)
101
- group_id = user_api.get_organization_id_from_group_name(group_name)
102
100
 
103
101
  # Safety checks
104
102
  try:
@@ -151,23 +149,20 @@ def _attack_create_or_run_lab(args: Any, do_run: bool = True):
151
149
  attack,
152
150
  scenario_profile,
153
151
  lab_config,
154
- group_name,
155
- group_id,
152
+ workspace_id,
156
153
  )
157
154
  else:
158
155
  lab_id = scenario_api.create_lab_attack(
159
156
  attack,
160
157
  scenario_profile,
161
158
  lab_config,
162
- group_name,
163
- group_id,
159
+ workspace_id,
164
160
  )
165
161
 
166
- print(f"[+] Scenario lab ID: {lab_id}")
162
+ print(f"[+] Lab ID: {lab_id}")
167
163
 
168
164
  if do_run:
169
165
  wait_lab(lab_id)
170
- print("[+] Scenario ended")
171
166
 
172
167
  except Exception as e:
173
168
  print(colored(f"Error when running attack {attack_name}: '{e}'", "red"))
@@ -251,8 +246,8 @@ def add_attack_parser(root_parser: argparse.ArgumentParser, subparsers: Any) ->
251
246
  help="Allows to define the unit scenario to create_lab for a unit attack",
252
247
  )
253
248
  parser_attack_create_lab.add_argument(
254
- "--group_name",
255
- dest="group_name",
249
+ "--workspace_id",
250
+ dest="workspace_id",
256
251
  help="The group name that have ownership on lab",
257
252
  )
258
253
  parser_attack_create_lab.add_argument(
@@ -285,8 +280,8 @@ def add_attack_parser(root_parser: argparse.ArgumentParser, subparsers: Any) ->
285
280
  help="Allows to define the unit scenario to run for a unit attack",
286
281
  )
287
282
  parser_attack_run_lab.add_argument(
288
- "--group_name",
289
- dest="group_name",
283
+ "--workspace_id",
284
+ dest="workspace_id",
290
285
  help="The group name that have ownership on lab",
291
286
  )
292
287
  parser_attack_run_lab.add_argument(