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.
- mantis_api_client/__init__.py +21 -0
- mantis_api_client/cli_parser/account_parser.py +451 -0
- mantis_api_client/cli_parser/attack_parser.py +317 -0
- mantis_api_client/cli_parser/bas_parser.py +1204 -0
- mantis_api_client/cli_parser/basebox_parser.py +258 -0
- mantis_api_client/cli_parser/dataset_parser.py +200 -0
- mantis_api_client/cli_parser/lab_parser.py +805 -0
- mantis_api_client/cli_parser/labs_parser.py +87 -0
- mantis_api_client/cli_parser/log_collector_parser.py +71 -0
- mantis_api_client/cli_parser/redteam_parser.py +375 -0
- mantis_api_client/cli_parser/scenario_parser.py +311 -0
- mantis_api_client/cli_parser/signature_parser.py +147 -0
- mantis_api_client/cli_parser/topology_parser.py +255 -0
- mantis_api_client/cli_parser/user_parser.py +225 -0
- mantis_api_client/config.py +73 -0
- mantis_api_client/dataset_api.py +267 -0
- mantis_api_client/exceptions.py +27 -0
- mantis_api_client/mantis.py +186 -0
- mantis_api_client/oidc.py +302 -0
- mantis_api_client/scenario_api.py +1196 -0
- mantis_api_client/user_api.py +282 -0
- mantis_api_client/utils.py +130 -0
- mantis_api_client-5.5.0.dist-info/AUTHORS +1 -0
- mantis_api_client-5.5.0.dist-info/LICENSE +19 -0
- mantis_api_client-5.5.0.dist-info/METADATA +33 -0
- mantis_api_client-5.5.0.dist-info/RECORD +28 -0
- mantis_api_client-5.5.0.dist-info/WHEEL +4 -0
- mantis_api_client-5.5.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Component version
|
|
8
|
+
__version__ = "4.8.0"
|
|
9
|
+
|
|
10
|
+
# Remove incompatible env variable
|
|
11
|
+
os.environ.pop("USE_PERMISSIONS", None)
|
|
12
|
+
|
|
13
|
+
# Get component full version from file generated at build time
|
|
14
|
+
current_file_dir = Path(__file__).resolve().parent
|
|
15
|
+
fullversion_file = Path(current_file_dir, "fullversion.txt")
|
|
16
|
+
if os.path.isfile(fullversion_file):
|
|
17
|
+
__fullversion__ = open(fullversion_file, "r").read().strip()
|
|
18
|
+
else:
|
|
19
|
+
__fullversion__ = __version__
|
|
20
|
+
|
|
21
|
+
shutil_make_archive_lock = threading.Lock()
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import getpass
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from http.server import BaseHTTPRequestHandler
|
|
9
|
+
from http.server import HTTPServer
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from threading import Thread
|
|
12
|
+
from typing import Any
|
|
13
|
+
from typing import List
|
|
14
|
+
from urllib.parse import parse_qs
|
|
15
|
+
from urllib.parse import urlparse
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
from mantis_authz import jwt
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.prompt import Prompt
|
|
21
|
+
from rich.tree import Tree
|
|
22
|
+
|
|
23
|
+
import mantis_api_client
|
|
24
|
+
import mantis_api_client.dataset_api as dataset_api
|
|
25
|
+
import mantis_api_client.scenario_api as scenario_api
|
|
26
|
+
from mantis_api_client import user_api
|
|
27
|
+
from mantis_api_client.config import mantis_api_client_config
|
|
28
|
+
from mantis_api_client.oidc import get_oidc_client
|
|
29
|
+
from mantis_api_client.utils import colored
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Version:
|
|
33
|
+
def __init__(self, str_vers: str) -> None:
|
|
34
|
+
try:
|
|
35
|
+
self.major, self.minor, self.patch = str_vers.split(".", 2)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise Exception(
|
|
38
|
+
"Bad version format for '{}': 'X.Y.Z' expected. Error: {}".format(
|
|
39
|
+
str_vers, e
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# 'status' related functions
|
|
46
|
+
#
|
|
47
|
+
def status_handler(args: Any) -> None: # noqa: C901
|
|
48
|
+
"""Get platform status."""
|
|
49
|
+
|
|
50
|
+
exit_code = 0
|
|
51
|
+
|
|
52
|
+
client_version = mantis_api_client.__version__
|
|
53
|
+
client_vers = Version(str_vers=client_version)
|
|
54
|
+
client_fullversion = mantis_api_client.__fullversion__
|
|
55
|
+
print(
|
|
56
|
+
f"[+] mantis_api_client version: {client_version} ({client_fullversion})".format(
|
|
57
|
+
client_version
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
active_profile_domain = get_oidc_client().get_active_profile_domain(raise_exc=False)
|
|
62
|
+
if active_profile_domain:
|
|
63
|
+
print(f"[+] Authenticated to {active_profile_domain}")
|
|
64
|
+
else:
|
|
65
|
+
print(
|
|
66
|
+
colored(
|
|
67
|
+
"[+] Not authenticated, you need to execute 'mantis account login'",
|
|
68
|
+
"red",
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
print("[+] APIs status")
|
|
73
|
+
|
|
74
|
+
# Dataset API
|
|
75
|
+
print(" [+] Dataset API")
|
|
76
|
+
print(" [+] address: {}".format(mantis_api_client_config.dataset_api_url))
|
|
77
|
+
try:
|
|
78
|
+
dataset_api_version = dataset_api.get_version()
|
|
79
|
+
dataset_vers = Version(str_vers=dataset_api_version)
|
|
80
|
+
except requests.exceptions.ConnectionError:
|
|
81
|
+
exit_code = 1
|
|
82
|
+
print(" [-] API status: " + colored("not running !", "white", "on_red"))
|
|
83
|
+
else:
|
|
84
|
+
print(" [+] API status: " + colored("OK", "grey", "on_green"))
|
|
85
|
+
print(" [+] version: {}".format(dataset_api_version))
|
|
86
|
+
if dataset_vers.major != client_vers.major:
|
|
87
|
+
exit_code = 1
|
|
88
|
+
print(
|
|
89
|
+
" [-] "
|
|
90
|
+
+ colored(
|
|
91
|
+
"Error: Dataset API major version ({}) mismatchs with mantis_api_client major version ({})".format(
|
|
92
|
+
dataset_vers.major, client_vers.major
|
|
93
|
+
),
|
|
94
|
+
"white",
|
|
95
|
+
"on_red",
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Scenario API
|
|
100
|
+
print(" [+] Scenario API")
|
|
101
|
+
print(" [+] address: {}".format(mantis_api_client_config.scenario_api_url))
|
|
102
|
+
try:
|
|
103
|
+
scenario_api_version = scenario_api.get_version()
|
|
104
|
+
scenario_vers = Version(str_vers=scenario_api_version)
|
|
105
|
+
cyber_range_version = scenario_api.get_cyberrange_version()
|
|
106
|
+
except requests.exceptions.ConnectionError:
|
|
107
|
+
exit_code = 1
|
|
108
|
+
print(" [-] API status: " + colored("not running !", "white", "on_red"))
|
|
109
|
+
else:
|
|
110
|
+
print(" [+] API status: " + colored("OK", "grey", "on_green"))
|
|
111
|
+
print(" [+] version: {}".format(scenario_api_version))
|
|
112
|
+
print(" [+] Cyber Range version: {}".format(cyber_range_version))
|
|
113
|
+
if scenario_vers.major != client_vers.major:
|
|
114
|
+
exit_code = 1
|
|
115
|
+
print(
|
|
116
|
+
" [-] "
|
|
117
|
+
+ colored(
|
|
118
|
+
"Error: Scenario API major version ({}) mismatchs with mantis_api_client major version ({})".format(
|
|
119
|
+
scenario_vers.major, client_vers.major
|
|
120
|
+
),
|
|
121
|
+
"white",
|
|
122
|
+
"on_red",
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# User API
|
|
127
|
+
print(" [+] Backoffice API")
|
|
128
|
+
print(" [+] address: {}".format(mantis_api_client_config.user_api_url))
|
|
129
|
+
try:
|
|
130
|
+
user_api_version = user_api.get_version()
|
|
131
|
+
user_vers = Version(str_vers=user_api_version)
|
|
132
|
+
except requests.exceptions.ConnectionError:
|
|
133
|
+
exit_code = 1
|
|
134
|
+
print(" [-] API status: " + colored("not running !", "white", "on_red"))
|
|
135
|
+
else:
|
|
136
|
+
print(" [+] API status: " + colored("OK", "grey", "on_green"))
|
|
137
|
+
print(" [+] version: {}".format(user_api_version))
|
|
138
|
+
if user_vers.major != client_vers.major:
|
|
139
|
+
exit_code = 1
|
|
140
|
+
print(
|
|
141
|
+
" [-] "
|
|
142
|
+
+ colored(
|
|
143
|
+
"Error: User API major version ({}) mismatchs with mantis_api_client major version ({})".format(
|
|
144
|
+
user_vers.major, client_vers.major
|
|
145
|
+
),
|
|
146
|
+
"white",
|
|
147
|
+
"on_red",
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if exit_code != 0:
|
|
152
|
+
sys.exit(exit_code)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
# 'info' related functions
|
|
157
|
+
#
|
|
158
|
+
def info_handler(args: Any) -> None: # noqa: C901
|
|
159
|
+
"""Get personal info."""
|
|
160
|
+
|
|
161
|
+
active_profile_domain = get_oidc_client().get_active_profile_domain(raise_exc=False)
|
|
162
|
+
if not active_profile_domain:
|
|
163
|
+
print("[+] Not authenticated")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
console = Console(highlight=False)
|
|
167
|
+
rprint = console.print
|
|
168
|
+
active_tokens = get_oidc_client().get_active_tokens()
|
|
169
|
+
access_token = jwt.get_unverified_claims(active_tokens["access_token"])
|
|
170
|
+
id_token = jwt.get_unverified_claims(active_tokens["id_token"])
|
|
171
|
+
|
|
172
|
+
sub_orgs = user_api.fetch_current_seats()
|
|
173
|
+
sub_membership: dict[str, list[dict]] = {}
|
|
174
|
+
sub_default_ws = get_oidc_client().get_default_workspace()
|
|
175
|
+
# Handle case where the user pertains to an organization
|
|
176
|
+
if sub_orgs:
|
|
177
|
+
sub_wss = user_api.fetch_current_workspaces()
|
|
178
|
+
for sub_org in sub_orgs:
|
|
179
|
+
sub_org_wss: list[dict] = []
|
|
180
|
+
sub_membership[sub_org["name"]] = sub_org_wss
|
|
181
|
+
for sub_ws in sub_wss:
|
|
182
|
+
if sub_org["id"] == sub_ws["organization_id"]:
|
|
183
|
+
sub_org_wss.append(sub_ws)
|
|
184
|
+
|
|
185
|
+
sorted_scopes = ", ".join(sorted(access_token["scope"].split()))
|
|
186
|
+
|
|
187
|
+
print(f"[+] Connected to {active_profile_domain}")
|
|
188
|
+
print(f" [+] Username: {id_token['preferred_username']}")
|
|
189
|
+
print(f" [+] Email: {id_token.get('email', 'N/A')}")
|
|
190
|
+
if sub_orgs:
|
|
191
|
+
|
|
192
|
+
def hl_activated_ws(ws: dict) -> str:
|
|
193
|
+
if ws["id"] == sub_default_ws:
|
|
194
|
+
return f"[bold]{ws['name']}[/bold]"
|
|
195
|
+
return ws["name"]
|
|
196
|
+
|
|
197
|
+
rprint(
|
|
198
|
+
r" \[+] Organization membership: {}".format(
|
|
199
|
+
", ".join(
|
|
200
|
+
"{} ({})".format(
|
|
201
|
+
org,
|
|
202
|
+
", ".join(map(hl_activated_ws, org_wss)),
|
|
203
|
+
)
|
|
204
|
+
for org, org_wss in sub_membership.items()
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
print(f" [+] Scopes: {sorted_scopes}")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# 'login_handler' handler
|
|
213
|
+
#
|
|
214
|
+
def login_handler(args: Any) -> None:
|
|
215
|
+
oidc_client = get_oidc_client()
|
|
216
|
+
# Parameters
|
|
217
|
+
oidc_domain = args.domain
|
|
218
|
+
username = args.username
|
|
219
|
+
if args.password_stdin:
|
|
220
|
+
password = sys.stdin.read().rstrip()
|
|
221
|
+
elif args.password_fd:
|
|
222
|
+
with os.fdopen(args.password_fd) as f:
|
|
223
|
+
password = f.read().rstrip()
|
|
224
|
+
elif args.password_file:
|
|
225
|
+
password = args.password_file.read_text().rstrip()
|
|
226
|
+
elif args.username:
|
|
227
|
+
password = getpass.getpass()
|
|
228
|
+
else:
|
|
229
|
+
password = None
|
|
230
|
+
|
|
231
|
+
scope = " ".join(
|
|
232
|
+
[
|
|
233
|
+
"openid",
|
|
234
|
+
"offline_access",
|
|
235
|
+
"groups",
|
|
236
|
+
"profile",
|
|
237
|
+
"email",
|
|
238
|
+
"scenario:run",
|
|
239
|
+
]
|
|
240
|
+
)
|
|
241
|
+
redirect_uri = mantis_api_client_config.oidc.redirect_uri
|
|
242
|
+
redirect_auto = redirect_uri.startswith("http")
|
|
243
|
+
code_placeholder: List[str] = []
|
|
244
|
+
if redirect_auto:
|
|
245
|
+
thread = _init_create_callback_request_handler_thread(code_placeholder)
|
|
246
|
+
thread.start()
|
|
247
|
+
if username and password:
|
|
248
|
+
token = oidc_client.token(
|
|
249
|
+
oidc_domain, username, password, redirect_uri=redirect_uri, scope=scope
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
auth_url = oidc_client.auth_url(
|
|
253
|
+
oidc_domain,
|
|
254
|
+
redirect_uri=redirect_uri,
|
|
255
|
+
scope=scope,
|
|
256
|
+
)
|
|
257
|
+
if shutil.which("xdg-open") is not None:
|
|
258
|
+
subprocess.run(["xdg-open", auth_url])
|
|
259
|
+
print(f"A web browser should have been opened for {oidc_domain!r}")
|
|
260
|
+
else:
|
|
261
|
+
print(
|
|
262
|
+
f"Open this URL in a web browser in order to create an access token for M&NTIS:\n\n{auth_url}\n"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if redirect_auto:
|
|
266
|
+
timeout = mantis_api_client_config.oidc.redirect_url_timeout
|
|
267
|
+
print(f"Waiting callback for {timeout}s")
|
|
268
|
+
thread.join(timeout)
|
|
269
|
+
if thread.is_alive():
|
|
270
|
+
print("Timeout exceeded, exiting")
|
|
271
|
+
print("Access NOT granted")
|
|
272
|
+
exit(1)
|
|
273
|
+
code = code_placeholder[0]
|
|
274
|
+
else:
|
|
275
|
+
code = input("Paste the code displayed in the webpage: ")
|
|
276
|
+
token = get_oidc_client().token(
|
|
277
|
+
oidc_domain,
|
|
278
|
+
grant_type="authorization_code",
|
|
279
|
+
code=code,
|
|
280
|
+
redirect_uri=redirect_uri,
|
|
281
|
+
)
|
|
282
|
+
print("Access granted")
|
|
283
|
+
oidc_client.configure_profile(oidc_domain, token["refresh_token"])
|
|
284
|
+
|
|
285
|
+
# Handle case where the user does not pertain to an organization
|
|
286
|
+
subject_workspaces = user_api.fetch_current_workspaces()
|
|
287
|
+
subject_organizations = user_api.fetch_current_seats()
|
|
288
|
+
subject_orgs_wss = [
|
|
289
|
+
(sub_org, sub_ws)
|
|
290
|
+
for sub_org in subject_organizations
|
|
291
|
+
for sub_ws in subject_workspaces
|
|
292
|
+
if sub_ws["organization_id"] == sub_org["id"]
|
|
293
|
+
]
|
|
294
|
+
selected_idx: int | None = None
|
|
295
|
+
if args.workspace:
|
|
296
|
+
for i, (_, ws) in enumerate(subject_orgs_wss):
|
|
297
|
+
if args.workspace == ws["id"]:
|
|
298
|
+
selected_idx = i
|
|
299
|
+
console = Console()
|
|
300
|
+
if selected_idx is None:
|
|
301
|
+
match len(subject_orgs_wss):
|
|
302
|
+
case 0:
|
|
303
|
+
return
|
|
304
|
+
case 1:
|
|
305
|
+
selected_idx = 0
|
|
306
|
+
case _:
|
|
307
|
+
i = 1
|
|
308
|
+
root = Tree(":file_folder:[yellow]Workspace memberships")
|
|
309
|
+
for org in subject_organizations:
|
|
310
|
+
org_branch = root.add(f"Organization [magenta]{org['name']}")
|
|
311
|
+
for ws in subject_workspaces:
|
|
312
|
+
if ws["organization_id"] != org["id"]:
|
|
313
|
+
continue
|
|
314
|
+
org_branch.add(rf"[bold green]{i}[/bold green]. {ws['name']}")
|
|
315
|
+
i += 1
|
|
316
|
+
console.print(root)
|
|
317
|
+
selected_idx = (
|
|
318
|
+
int(
|
|
319
|
+
Prompt.ask(
|
|
320
|
+
"Select a default workspace",
|
|
321
|
+
choices=[str(k + 1) for k in range(len(subject_orgs_wss))],
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
- 1
|
|
325
|
+
)
|
|
326
|
+
selected_ws = subject_orgs_wss[selected_idx][1]
|
|
327
|
+
console.print(f"Workspace [b green]{selected_ws['name']}[/b green] activated")
|
|
328
|
+
|
|
329
|
+
oidc_client.configure_profile(
|
|
330
|
+
oidc_domain, token["refresh_token"], selected_ws["id"]
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
#
|
|
335
|
+
# 'logout_handler' handler
|
|
336
|
+
#
|
|
337
|
+
def logout_handler(args: Any) -> None:
|
|
338
|
+
get_oidc_client().configure_profile(None)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _init_create_callback_request_handler_thread(code_placeholder: list) -> Thread:
|
|
342
|
+
class CallbackHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
343
|
+
def do_GET(self) -> None:
|
|
344
|
+
query = urlparse(self.path).query
|
|
345
|
+
if "error" in query:
|
|
346
|
+
self.send_response(404)
|
|
347
|
+
else:
|
|
348
|
+
code = parse_qs(query)["code"][0]
|
|
349
|
+
code_placeholder.append(code)
|
|
350
|
+
self.send_response(200)
|
|
351
|
+
self.send_header("Content-Type", "text/html")
|
|
352
|
+
self.end_headers()
|
|
353
|
+
self.wfile.write(
|
|
354
|
+
"<script>window.close()</script> Authorization {}. You may close this window.\r\n".format(
|
|
355
|
+
"failed" if "error" in query else "succeeded"
|
|
356
|
+
).encode()
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def log_request(*args, **kwargs):
|
|
360
|
+
# do nothing
|
|
361
|
+
pass
|
|
362
|
+
|
|
363
|
+
def serve_one_redirect_callback() -> None:
|
|
364
|
+
with HTTPServer(
|
|
365
|
+
(
|
|
366
|
+
mantis_api_client_config.oidc.redirect_url.host,
|
|
367
|
+
mantis_api_client_config.oidc.redirect_url.port,
|
|
368
|
+
),
|
|
369
|
+
CallbackHTTPRequestHandler,
|
|
370
|
+
) as server:
|
|
371
|
+
server.handle_request()
|
|
372
|
+
|
|
373
|
+
return Thread(target=serve_one_redirect_callback, daemon=True)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def add_account_parser(root_parser: argparse.ArgumentParser, subparsers: Any) -> None:
|
|
377
|
+
# -------------------
|
|
378
|
+
# --- login options
|
|
379
|
+
# -------------------
|
|
380
|
+
|
|
381
|
+
parser_account = subparsers.add_parser(
|
|
382
|
+
"account",
|
|
383
|
+
help="Authentication actions for M&NTIS CLI.",
|
|
384
|
+
formatter_class=root_parser.formatter_class,
|
|
385
|
+
)
|
|
386
|
+
subparsers_account = parser_account.add_subparsers()
|
|
387
|
+
|
|
388
|
+
# 'status' command
|
|
389
|
+
parser_status = subparsers_account.add_parser(
|
|
390
|
+
"status",
|
|
391
|
+
help="Get platform status",
|
|
392
|
+
formatter_class=root_parser.formatter_class,
|
|
393
|
+
)
|
|
394
|
+
parser_status.set_defaults(func=status_handler)
|
|
395
|
+
|
|
396
|
+
# 'info' command
|
|
397
|
+
parser_info = subparsers_account.add_parser(
|
|
398
|
+
"info", help="Get personal info", formatter_class=root_parser.formatter_class
|
|
399
|
+
)
|
|
400
|
+
parser_info.set_defaults(func=info_handler)
|
|
401
|
+
|
|
402
|
+
parser_login = subparsers_account.add_parser(
|
|
403
|
+
"login",
|
|
404
|
+
help="Log into a M&ntis account",
|
|
405
|
+
formatter_class=root_parser.formatter_class,
|
|
406
|
+
)
|
|
407
|
+
parser_login.add_argument(
|
|
408
|
+
"--domain",
|
|
409
|
+
help="The M&ntis cluster SSO domain (default: %(default)s)",
|
|
410
|
+
default="mantis-platform.io",
|
|
411
|
+
)
|
|
412
|
+
parser_login.add_argument(
|
|
413
|
+
"--username",
|
|
414
|
+
"-u",
|
|
415
|
+
help="Your M&ntis cluster SSO username",
|
|
416
|
+
)
|
|
417
|
+
parser_login_mex_group = parser_login.add_mutually_exclusive_group()
|
|
418
|
+
parser_login_mex_group.add_argument(
|
|
419
|
+
"--password-stdin",
|
|
420
|
+
action="store_true",
|
|
421
|
+
help="Read your M&ntis cluster SSO password from stdin",
|
|
422
|
+
)
|
|
423
|
+
parser_login_mex_group.add_argument(
|
|
424
|
+
"--password-fd",
|
|
425
|
+
type=int,
|
|
426
|
+
help="Read your M&ntis cluster SSO password from a descriptor",
|
|
427
|
+
)
|
|
428
|
+
parser_login_mex_group.add_argument(
|
|
429
|
+
"--password-file",
|
|
430
|
+
type=Path,
|
|
431
|
+
help="Read your M&ntis cluster SSO password from a file",
|
|
432
|
+
)
|
|
433
|
+
parser_login.add_argument(
|
|
434
|
+
"-w",
|
|
435
|
+
"--workspace",
|
|
436
|
+
help="Pass the workspace that will be used as default for workspace-aware commands",
|
|
437
|
+
)
|
|
438
|
+
parser_login.set_defaults(func=login_handler)
|
|
439
|
+
|
|
440
|
+
# -------------------
|
|
441
|
+
# --- logout options
|
|
442
|
+
# -------------------
|
|
443
|
+
|
|
444
|
+
parser_logout = subparsers_account.add_parser(
|
|
445
|
+
"logout",
|
|
446
|
+
help="Log out from your M&ntis account",
|
|
447
|
+
formatter_class=root_parser.formatter_class,
|
|
448
|
+
)
|
|
449
|
+
parser_logout.set_defaults(func=logout_handler)
|
|
450
|
+
|
|
451
|
+
parser_account.set_defaults(func=lambda _: parser_account.print_help())
|