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,225 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+
7
+ from pydantic.json import pydantic_encoder
8
+
9
+ from mantis_api_client import user_api
10
+ from mantis_api_client.oidc import get_oidc_client
11
+ from mantis_api_client.utils import colored
12
+
13
+
14
+ # #
15
+ # # 'user_list_handler' handler
16
+ # #
17
+ # def user_list_handler(args: Any) -> None:
18
+ # try:
19
+ # users = user_api.fetch_users()
20
+ # except Exception as e:
21
+ # print(colored(f"Error when fetching users: '{e}'", "red"))
22
+ # sys.exit(1)
23
+
24
+ # if args.json:
25
+ # print(json.dumps(users, default=pydantic_encoder))
26
+ # return
27
+
28
+ # print("[+] Organization users:")
29
+ # for user in users["data"]:
30
+ # print(f" [+] {user['id']}: {user['given_name']} {user['last_name']}")
31
+
32
+
33
+ #
34
+ # 'user_self_handler' handler
35
+ #
36
+ def user_self_handler(args: Any) -> None:
37
+ try:
38
+ user = user_api.fetch_current_user()
39
+ except Exception as e:
40
+ print(colored(f"Error when fetching current user information: '{e}'", "red"))
41
+ sys.exit(1)
42
+
43
+ if args.json:
44
+ print(json.dumps(user, default=pydantic_encoder))
45
+ return
46
+
47
+ print("[+] Current user information:")
48
+ print(f" [+] ID: {user['id']}")
49
+ print(f" [+] Name: {user['given_name']} {user['last_name']}")
50
+ print(f" [+] Email: {user['email']}")
51
+ print(f" [+] Creation date: {user['creation_date']}")
52
+
53
+
54
+ #
55
+ # 'user_info_handler' handler
56
+ #
57
+ def user_info_handler(args: Any) -> None:
58
+ # Parameters
59
+ user_id = args.user_id
60
+
61
+ try:
62
+ user = user_api.fetch_user(user_id)
63
+ except Exception as e:
64
+ print(
65
+ colored(
66
+ f"Error when fetching information for user '{user_id}': '{e}'",
67
+ "red",
68
+ "on_white",
69
+ )
70
+ )
71
+ sys.exit(1)
72
+
73
+ if args.json:
74
+ print(json.dumps(user, default=pydantic_encoder))
75
+ return
76
+
77
+ print("[+] User information:")
78
+ print(f" [+] ID: {user['id']}")
79
+ print(f" [+] Name: {user['given_name']} {user['last_name']}")
80
+ print(f" [+] Email: {user['email']}")
81
+ print(f" [+] Creation date: {user['creation_date']}")
82
+
83
+
84
+ #
85
+ # 'organization_info_handler' handler
86
+ #
87
+ def organization_info_handler(args: Any) -> None:
88
+ try:
89
+ # Retrieve current organization name
90
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
91
+
92
+ # Retrieve associated organization id
93
+ if workspace_id is None:
94
+ print("Current user not linked to a workspace")
95
+ sys.exit(0)
96
+
97
+ # Retrieve organization info
98
+ workspaces_info = iter(user_api.fetch_current_workspaces())
99
+ organization_id = next(
100
+ workspace_info["organization_id"]
101
+ for workspace_info in workspaces_info
102
+ if workspace_info["id"] == workspace_id
103
+ )
104
+ organization_info = user_api.fetch_organization(organization_id)
105
+
106
+ # Retrieve plan info
107
+ plan_info = user_api.get_plan(organization_info["plan_id"])
108
+
109
+ # Retrieve plan limits
110
+ plan_limits = user_api.get_plan_limits(organization_info["plan_id"])
111
+
112
+ # Retrieve credits list
113
+ credits_list = user_api.get_credits_list(organization_id)
114
+
115
+ # Retrieve consumption history
116
+ consumption_history = user_api.get_consumption_history(organization_id)
117
+ except Exception as e:
118
+ print(
119
+ colored(
120
+ f"Error when fetching information for organization '{workspace_id}': '{e}'",
121
+ "red",
122
+ "on_white",
123
+ )
124
+ )
125
+ sys.exit(1)
126
+
127
+ if args.json:
128
+ print(json.dumps(organization_info, default=pydantic_encoder))
129
+ print(json.dumps(plan_info, default=pydantic_encoder))
130
+ print(json.dumps(plan_limits, default=pydantic_encoder))
131
+ print(json.dumps(credits_list, default=pydantic_encoder))
132
+ print(json.dumps(consumption_history, default=pydantic_encoder))
133
+ return
134
+
135
+ print("[+] Organization information:")
136
+ print(f" [+] ID: {organization_info['id']}")
137
+ print(f" [+] Name: {organization_info['name']}")
138
+ print(f" [+] Subscription start: {organization_info['subscription_start']}")
139
+ print(f" [+] Subscription end: {organization_info['subscription_end']}")
140
+
141
+ print("[+] Plan information:")
142
+ print(f" [+] ID: {plan_info['id']}")
143
+ print(f" [+] Name: {plan_info['name']}")
144
+ print(f" [+] Frequency: {plan_info['frequency']}")
145
+
146
+ print("[+] Plan limits:")
147
+ print(f" [+] Max active labs: {plan_limits['nb_active_labs']}")
148
+ print(f" [+] Max accounts: {plan_limits['nb_accounts']}")
149
+ print(f" [+] Max workspaces: {plan_limits['nb_workspaces']}")
150
+ print(f" [+] Max lab duration: {plan_limits['lab_max_duration']}")
151
+
152
+ print("[+] Credits:")
153
+ if "data" in credits_list:
154
+ for credit in credits_list["data"]:
155
+ print(f" [+] ID: {credit['id']}")
156
+ print(f" [+] Purpose: {credit['purpose']}")
157
+ print(
158
+ f" [+] Period: {credit['creation_date']} to {credit['expiration_date']}"
159
+ )
160
+ print(f" [+] Credit: {credit['remaining']}/{credit['total']}")
161
+ else:
162
+ print(" [+] No credits")
163
+
164
+
165
+ def add_user_parser(
166
+ root_parser: argparse.ArgumentParser,
167
+ subparsers: Any,
168
+ ) -> None:
169
+ # --------------------
170
+ # --- User API options (users)
171
+ # --------------------
172
+
173
+ parser_user = subparsers.add_parser(
174
+ "user",
175
+ help="User API related commands",
176
+ formatter_class=root_parser.formatter_class,
177
+ )
178
+ subparsers_user = parser_user.add_subparsers()
179
+
180
+ # # 'user_list' command
181
+ # parser_user_list = subparsers_user.add_parser(
182
+ # "list",
183
+ # help="List all organization users",
184
+ # formatter_class=root_parser.formatter_class,
185
+ # )
186
+ # parser_user_list.set_defaults(func=user_list_handler)
187
+ # parser_user_list.add_argument(
188
+ # "--json", help="Return JSON result.", action="store_true"
189
+ # )
190
+
191
+ # 'user_self' command
192
+ parser_user_self = subparsers_user.add_parser(
193
+ "self",
194
+ help="Retrieve information for current user",
195
+ formatter_class=root_parser.formatter_class,
196
+ )
197
+ parser_user_self.set_defaults(func=user_self_handler)
198
+ parser_user_self.add_argument(
199
+ "--json", help="Return JSON result.", action="store_true"
200
+ )
201
+
202
+ # 'user_info' command
203
+ parser_user_info = subparsers_user.add_parser(
204
+ "info",
205
+ help="Retrieve information about a specific user",
206
+ formatter_class=root_parser.formatter_class,
207
+ )
208
+ parser_user_info.set_defaults(func=user_info_handler)
209
+ parser_user_info.add_argument("user_id", type=str, help="The user ID")
210
+ parser_user_info.add_argument(
211
+ "--json", help="Return JSON result.", action="store_true"
212
+ )
213
+
214
+ # 'orga_info' command
215
+ parser_user_info = subparsers_user.add_parser(
216
+ "organization",
217
+ help="Retrieve information about current organization",
218
+ formatter_class=root_parser.formatter_class,
219
+ )
220
+ parser_user_info.set_defaults(func=organization_info_handler)
221
+ parser_user_info.add_argument(
222
+ "--json", help="Return JSON result.", action="store_true"
223
+ )
224
+
225
+ parser_user.set_defaults(func=lambda _: parser_user.print_help())
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright (c) 2016-2021 AMOSSYS
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #
24
+ from dataclasses import dataclass
25
+ from dataclasses import field
26
+ from pathlib import Path
27
+ from typing import Optional
28
+
29
+ from mantis_authz.config import AuthzConfig
30
+ from omegaconf import OmegaConf
31
+ from omegaconf import SI
32
+
33
+
34
+ @dataclass
35
+ class MantisOIDCConfig(AuthzConfig):
36
+ realm: str = "mantis"
37
+ client_id: str = "mantis-cli"
38
+ domain: Optional[str] = None
39
+ redirect_url: dict = field(
40
+ default_factory=lambda: {
41
+ "scheme": "http",
42
+ "host": "localhost",
43
+ "port": 9876,
44
+ "path": "/callback",
45
+ }
46
+ )
47
+ redirect_url_timeout: int = SI(
48
+ "${oc.decode:${oc.env:OIDC_REDIRECT_URL_TIMEOUT,60}}"
49
+ )
50
+ redirect_uri: str = (
51
+ "${oc.env:OIDC_REDIRECT_URI,${.redirect_url.scheme}://${.redirect_url.host}:${.redirect_url.port}${.redirect_url.path}}"
52
+ )
53
+
54
+
55
+ @dataclass
56
+ class MantisApiClientConfig:
57
+ dataset_api_url: str
58
+ dataset_web_url: str
59
+ scenario_api_url: str
60
+ user_api_url: str
61
+ cacert: Optional[Path] = SI("${oc.env:CR_CA_CERT,null}")
62
+ cert: Optional[Path] = SI("${oc.env:CR_CLIENT_CERT,null}")
63
+ key: Optional[Path] = SI("${oc.env:CR_CLIENT_KEY,null}")
64
+
65
+ config_file: Path = SI(
66
+ "${oc.env:MANTIS_CONFIG,${oc.env:XDG_CONFIG_HOME,${oc.env:HOME}/.config}/mantis/config.yml}"
67
+ )
68
+
69
+ # OIDC
70
+ oidc: MantisOIDCConfig = field(default_factory=MantisOIDCConfig)
71
+
72
+
73
+ mantis_api_client_config = OmegaConf.structured(MantisApiClientConfig)
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright (c) 2016-2021 AMOSSYS
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #
24
+ from pathlib import Path
25
+ from typing import Any
26
+ from uuid import UUID
27
+
28
+ import requests
29
+ from mantis_dataset_model.dataset_model import Manifest
30
+
31
+ from mantis_api_client.config import mantis_api_client_config
32
+ from mantis_api_client.oidc import authorize
33
+
34
+ # -------------------------------------------------------------------------- #
35
+ # Internal helpers
36
+ # -------------------------------------------------------------------------- #
37
+
38
+
39
+ @authorize
40
+ def _get(route: str, **kwargs: Any) -> requests.Response:
41
+ return requests.get(
42
+ f"{mantis_api_client_config.dataset_api_url}{route}",
43
+ verify=mantis_api_client_config.cacert,
44
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
45
+ **kwargs,
46
+ )
47
+
48
+
49
+ @authorize
50
+ def _post(route: str, **kwargs: Any) -> requests.Response:
51
+ timeout = kwargs.pop("timeout", 30)
52
+ return requests.post(
53
+ f"{mantis_api_client_config.dataset_api_url}{route}",
54
+ verify=mantis_api_client_config.cacert,
55
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
56
+ timeout=timeout,
57
+ **kwargs,
58
+ )
59
+
60
+
61
+ @authorize
62
+ def _put(route: str, **kwargs: Any) -> requests.Response:
63
+ return requests.put(
64
+ f"{mantis_api_client_config.dataset_api_url}{route}",
65
+ verify=mantis_api_client_config.cacert,
66
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
67
+ **kwargs,
68
+ )
69
+
70
+
71
+ @authorize
72
+ def _delete(route: str, **kwargs: Any) -> requests.Response:
73
+ return requests.delete(
74
+ f"{mantis_api_client_config.dataset_api_url}{route}",
75
+ verify=mantis_api_client_config.cacert,
76
+ cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
77
+ **kwargs,
78
+ )
79
+
80
+
81
+ def _handle_error(
82
+ result: requests.Response, context_error_msg: str
83
+ ) -> requests.Response:
84
+ if (
85
+ result.headers.get("content-type") == "application/json"
86
+ and "message" in result.json()
87
+ ):
88
+ error_msg = result.json()["message"]
89
+ else:
90
+ error_msg = result.text
91
+
92
+ raise Exception(
93
+ f"{context_error_msg}. "
94
+ f"Status code: '{result.status_code}'.\n"
95
+ f"Error message: '{error_msg}'."
96
+ )
97
+
98
+
99
+ # -------------------------------------------------------------------------- #
100
+ # Internal helpers
101
+ # -------------------------------------------------------------------------- #
102
+
103
+
104
+ def get_version() -> str:
105
+ """
106
+ Return dataset API version.
107
+
108
+ :return: The version inumber n a string
109
+ """
110
+ result = _get("/version")
111
+
112
+ if result.status_code != 200:
113
+ _handle_error(result, "Cannot retrieve dataset API version")
114
+
115
+ return result.json()
116
+
117
+
118
+ # -------------------------------------------------------------------------- #
119
+ # Dataset API
120
+ # -------------------------------------------------------------------------- #
121
+
122
+
123
+ def fetch_datasets() -> Any:
124
+ """
125
+ List all available datasets
126
+
127
+ :return: the list of manifests
128
+ """
129
+ result = _get("/")
130
+
131
+ if result.status_code != 200:
132
+ _handle_error(result, "Cannot retrieve datasets from dataset API")
133
+
134
+ return result.json()
135
+
136
+
137
+ def fetch_dataset_by_uuid(partial_dataset_id: str) -> Any:
138
+ """
139
+ Get the full JSON manifest of a specific dataset
140
+
141
+ :param partial_dataset_id: UUID of the dataset to fetch, or prefix of it that uniquely identifies the dataset
142
+
143
+ If a full UUID is given, returns the identified dataset metadata if it
144
+ exists. If a partial UUID (prefix of an existing UUID) is given, and this
145
+ prefix uniquely identifies one and only one dataset, the latter's metadata
146
+ is returned.
147
+ """
148
+ result = _get(f"/{partial_dataset_id}")
149
+
150
+ if result.status_code != 200:
151
+ _handle_error(result, "Cannot retrieve dataset from dataset API")
152
+ else:
153
+ dataset_data = result.json()
154
+ dataset = Manifest(**dataset_data)
155
+
156
+ return dataset
157
+
158
+
159
+ def fetch_dataset_resource(
160
+ partial_dataset_id: str, resource_type_str: str, resource_id: UUID
161
+ ) -> Any:
162
+ """
163
+ Get the description of a specific resource.
164
+
165
+ This function *does not* fetch the file(s) associated with one resource,
166
+ it merely returns the portion of the JSON manifest that describes that
167
+ particular resource. This JSON includes, in particular, the list
168
+ of files included in the resource, and the URLs to download them. It is
169
+ up to the user to manually fetch these URLs.
170
+
171
+ :param partial_dataset_id: UUID of the dataset to which the resource belongs, or a prefix of it that uniquely identifies it
172
+ :param resource_type_str: type of the resource to fetch. Must be a string among "log", "pcap", "memory_dump", "redteam_report", "life_report"
173
+ :param resource_id: UUID of the resource
174
+ :return: JSON structure describing the resource
175
+ """
176
+ result = _get(f"/{partial_dataset_id}/{resource_type_str}/{resource_id}")
177
+
178
+ if result.status_code != 200:
179
+ _handle_error(result, "Cannot retrieve dataset from dataset API")
180
+
181
+ return result.json()
182
+
183
+
184
+ # def delete_dataset(
185
+ # dataset_id: str, force: bool = False
186
+ # ) -> None:
187
+ # """
188
+ # Delete a specific dataset
189
+
190
+ # :param dataset_id: the full UUID of the dataset to delete
191
+ # :param force: (optional, default False) if True, forces the deletion of the dataset, even if there are validation errors in the manifest, or in the dataset contents. If the manifest is corrupted, remotely stored resources may not be deleted even though delete_remote_data was set to True.
192
+
193
+ # """
194
+
195
+ # raise NotImplementedError("This API method is not currently available")
196
+
197
+ # # result = _delete(
198
+ # # f"/{dataset_id}",
199
+ # # params={"force": force},
200
+ # # )
201
+
202
+ # # if result.status_code != 200:
203
+ # # _handle_error(result, "Cannot delete dataset from dataset API")
204
+
205
+ # # return None
206
+
207
+
208
+ # def delete_all_datasets(force: bool = False) -> Any:
209
+ # """
210
+ # Delete all datasets
211
+
212
+ # :param force: (optional, default False) if True, forces the deletion of the datasets, even if there are validation errors in the manifests, or in the datasets contents. If a manifest is corrupted, remotely stored resources for that dataset may not be deleted even though delete_remote_data was set to True.
213
+ # :return: a JSON structure specifying the datasets that were successfully deleted, and the errors encountered
214
+
215
+ # """
216
+
217
+ # raise NotImplementedError("This API method is not currently available")
218
+
219
+ # # result = _delete(
220
+ # # "/all", params={"force": force}
221
+ # # )
222
+
223
+ # # if result.status_code != 200:
224
+ # # _handle_error(result, "Cannot delete datasets from dataset API")
225
+
226
+ # # return result.json()
227
+
228
+
229
+ def get_archive_infos_for_dataset(partial_dataset_id: str) -> Any:
230
+ """
231
+ Retrieve information about a zip archive for a partial_dataset_id.
232
+
233
+ :param partial_dataset_id: the partial or complete UUID of the dataset
234
+ :return: a JSON structure containing size, url and if the archive exists.
235
+ """
236
+ result = _get(f"/{partial_dataset_id}/zip")
237
+
238
+ if result.status_code != 200:
239
+ _handle_error(
240
+ result,
241
+ "Cannot retrieve dataset archive information from dataset API",
242
+ )
243
+
244
+ return result.json()
245
+
246
+
247
+ def upload_dataset(dataset_path: Path, workspace_id: UUID | None) -> UUID:
248
+ # Uploading the dataset
249
+ params = {"filename": dataset_path.name, "owner": workspace_id}
250
+ with dataset_path.open("rb") as f:
251
+ result = _post("/upload", params=params, data=f, timeout=None)
252
+
253
+ if result.status_code != 200:
254
+ _handle_error(
255
+ result,
256
+ "Cannot retrieve dataset archive information from dataset API",
257
+ )
258
+ dataset_id = UUID(result.json()["dataset_id"])
259
+
260
+ return dataset_id
261
+
262
+
263
+ def upload_dataset_status(dataset_id: UUID) -> Any:
264
+ # Getting information about the Dataset
265
+ state = _get(f"/upload/{dataset_id}/status")
266
+ state = state.json()
267
+ return state
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright (c) 2016-2021 AMOSSYS
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #
24
+
25
+
26
+ class ConfigException(Exception):
27
+ pass