pyntcli 0.1.92__py3-none-any.whl → 0.1.94__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.
- pyntcli/__init__.py +20 -1
- pyntcli/commands/burp.py +6 -6
- pyntcli/commands/command.py +6 -2
- pyntcli/commands/har.py +4 -0
- pyntcli/commands/listen.py +7 -2
- pyntcli/commands/newman.py +6 -9
- pyntcli/commands/postman.py +3 -1
- pyntcli/commands/pynt_cmd.py +103 -8
- pyntcli/commands/root.py +13 -3
- pyntcli/commands/util.py +33 -0
- pyntcli/main.py +3 -1
- pyntcli/pynt_docker/pynt_container.py +56 -21
- pyntcli/store/__init__.py +1 -1
- pyntcli/store/store.py +25 -4
- pyntcli/ui/prompt.py +53 -0
- {pyntcli-0.1.92.dist-info → pyntcli-0.1.94.dist-info}/METADATA +4 -4
- pyntcli-0.1.94.dist-info/RECORD +46 -0
- {pyntcli-0.1.92.dist-info → pyntcli-0.1.94.dist-info}/WHEEL +1 -1
- {pyntcli-0.1.92.dist-info → pyntcli-0.1.94.dist-info}/top_level.txt +1 -0
- tests/commands/test_pynt_cmd.py +178 -0
- tests/test_utils.py +75 -0
- pyntcli-0.1.92.dist-info/RECORD +0 -43
- /tests/auth/test_login.py → /ignoreTests/auth/login.py +0 -0
- {tests → ignoreTests}/conftest.py +0 -0
- /tests/store/test_cred_store.py → /ignoreTests/store/cred_store.py +0 -0
- {pyntcli-0.1.92.dist-info → pyntcli-0.1.94.dist-info}/entry_points.txt +0 -0
pyntcli/__init__.py
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.94"
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from logzio import sender
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_silent_logger():
|
|
9
|
+
logger = logging.getLogger("silent_logger")
|
|
10
|
+
logger.addHandler(logging.NullHandler())
|
|
11
|
+
return logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def patched_get_stdout_logger(debug):
|
|
15
|
+
return get_silent_logger()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
sender.get_stdout_logger = patched_get_stdout_logger
|
|
19
|
+
except Exception as e:
|
|
20
|
+
pass
|
pyntcli/commands/burp.py
CHANGED
|
@@ -147,7 +147,7 @@ def burp_usage():
|
|
|
147
147
|
.with_line("\t--insecure - Use when target uses self signed certificates")
|
|
148
148
|
.with_line("\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io")
|
|
149
149
|
.with_line("\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN.")
|
|
150
|
-
.with_line("\t--
|
|
150
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default) ")
|
|
151
151
|
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
152
152
|
)
|
|
153
153
|
|
|
@@ -171,11 +171,7 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
171
171
|
burp_cmd.add_argument("--ca-path", type=str, default="")
|
|
172
172
|
burp_cmd.add_argument("--report", type=str, default="")
|
|
173
173
|
burp_cmd.add_argument("--captured-domains", nargs="+", help="", default="")
|
|
174
|
-
burp_cmd.add_argument(
|
|
175
|
-
"--return-error",
|
|
176
|
-
choices=["all-findings", "errors-only", "never"],
|
|
177
|
-
default="never",
|
|
178
|
-
)
|
|
174
|
+
burp_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
179
175
|
burp_cmd.print_usage = self.print_usage
|
|
180
176
|
burp_cmd.print_help = self.print_usage
|
|
181
177
|
return burp_cmd
|
|
@@ -296,7 +292,10 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
296
292
|
base_container=container,
|
|
297
293
|
use_native=args.use_docker_native)
|
|
298
294
|
|
|
295
|
+
proxy_docker.prepare_client()
|
|
296
|
+
proxy_docker.pre_run_validation(args.port)
|
|
299
297
|
proxy_docker.run()
|
|
298
|
+
|
|
300
299
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
301
300
|
|
|
302
301
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
@@ -355,3 +354,4 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
355
354
|
json_obj = json.loads(json_report)
|
|
356
355
|
if json_obj:
|
|
357
356
|
util.check_for_findings_or_warnings(args, json_obj)
|
|
357
|
+
util.check_severity(args.severity_level, json_obj)
|
pyntcli/commands/command.py
CHANGED
|
@@ -42,7 +42,7 @@ def command_usage():
|
|
|
42
42
|
.with_line("\t--no-proxy-export - Pynt will not export the proxy settings to the environment")
|
|
43
43
|
.with_line("\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io")
|
|
44
44
|
.with_line("\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN.")
|
|
45
|
-
.with_line("\t--
|
|
45
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default) ")
|
|
46
46
|
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
47
47
|
)
|
|
48
48
|
|
|
@@ -70,7 +70,7 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
70
70
|
proxy_cmd.add_argument("--ca-path", type=str, default="")
|
|
71
71
|
proxy_cmd.add_argument("--self-signed", action="store_true")
|
|
72
72
|
proxy_cmd.add_argument("--report", type=str, default="")
|
|
73
|
-
proxy_cmd.add_argument("--
|
|
73
|
+
proxy_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
74
74
|
proxy_cmd.print_usage = self.print_usage
|
|
75
75
|
proxy_cmd.print_help = self.print_usage
|
|
76
76
|
return proxy_cmd
|
|
@@ -183,7 +183,10 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
183
183
|
base_container=container,
|
|
184
184
|
use_native=args.use_docker_native)
|
|
185
185
|
|
|
186
|
+
proxy_docker.prepare_client()
|
|
187
|
+
proxy_docker.pre_run_validation(args.port)
|
|
186
188
|
proxy_docker.run()
|
|
189
|
+
|
|
187
190
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
188
191
|
|
|
189
192
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
@@ -261,3 +264,4 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
261
264
|
json_obj = json.loads(json_report)
|
|
262
265
|
if json_obj:
|
|
263
266
|
util.check_for_findings_or_warnings(args, json_obj)
|
|
267
|
+
util.check_severity(args.severity_level, json_obj)
|
pyntcli/commands/har.py
CHANGED
|
@@ -30,6 +30,7 @@ def har_usage():
|
|
|
30
30
|
.with_line(
|
|
31
31
|
"\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
32
32
|
)
|
|
33
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default) ")
|
|
33
34
|
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
34
35
|
.with_line("")
|
|
35
36
|
)
|
|
@@ -49,6 +50,7 @@ class HarSubCommand(sub_command.PyntSubCommand):
|
|
|
49
50
|
"--captured-domains", nargs="+", help="", default="", required=True
|
|
50
51
|
)
|
|
51
52
|
har_cmd.add_argument("--reporters", action="store_true")
|
|
53
|
+
har_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
52
54
|
har_cmd.print_usage = self.usage
|
|
53
55
|
har_cmd.print_help = self.usage
|
|
54
56
|
return har_cmd
|
|
@@ -91,6 +93,8 @@ class HarSubCommand(sub_command.PyntSubCommand):
|
|
|
91
93
|
base_container=container,
|
|
92
94
|
use_native=args.use_docker_native)
|
|
93
95
|
|
|
96
|
+
har_docker.prepare_client()
|
|
97
|
+
har_docker.pre_run_validation(port)
|
|
94
98
|
har_docker.run()
|
|
95
99
|
|
|
96
100
|
healthcheck = partial(
|
pyntcli/commands/listen.py
CHANGED
|
@@ -36,7 +36,7 @@ def listen_usage():
|
|
|
36
36
|
.with_line("\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io")
|
|
37
37
|
.with_line("\t--insecure - use when target uses self signed certificates")
|
|
38
38
|
.with_line("\t--host-ca - path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN.")
|
|
39
|
-
.with_line("\t--
|
|
39
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default) ")
|
|
40
40
|
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
41
41
|
)
|
|
42
42
|
|
|
@@ -61,7 +61,7 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
61
61
|
listen_cmd.add_argument("--allow-errors", action="store_true")
|
|
62
62
|
listen_cmd.add_argument("--ca-path", type=str, default="")
|
|
63
63
|
listen_cmd.add_argument("--report", type=str, default="")
|
|
64
|
-
listen_cmd.add_argument("--
|
|
64
|
+
listen_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
65
65
|
listen_cmd.print_usage = self.print_usage
|
|
66
66
|
listen_cmd.print_help = self.print_usage
|
|
67
67
|
return listen_cmd
|
|
@@ -140,7 +140,11 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
140
140
|
tag="proxy-latest",
|
|
141
141
|
detach=True,
|
|
142
142
|
base_container=container)
|
|
143
|
+
|
|
144
|
+
proxy_docker.prepare_client()
|
|
145
|
+
proxy_docker.pre_run_validation(args.port)
|
|
143
146
|
proxy_docker.run()
|
|
147
|
+
|
|
144
148
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
145
149
|
|
|
146
150
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
@@ -208,3 +212,4 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
208
212
|
json_obj = json.loads(json_report)
|
|
209
213
|
if json_obj:
|
|
210
214
|
util.check_for_findings_or_warnings(args, json_obj)
|
|
215
|
+
util.check_severity(args.severity_level, json_obj)
|
pyntcli/commands/newman.py
CHANGED
|
@@ -30,9 +30,7 @@ def newman_usage():
|
|
|
30
30
|
.with_line(
|
|
31
31
|
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
32
32
|
)
|
|
33
|
-
.with_line(
|
|
34
|
-
"\t--return-error - 'all-findings' (warnings, or errors), 'errors-only', 'never' (default) "
|
|
35
|
-
)
|
|
33
|
+
.with_line("\t--severity-level - 'all', 'medium', 'high', 'critical', 'none' (default) ")
|
|
36
34
|
.with_line("\t--verbose - Use to get more detailed information about the run")
|
|
37
35
|
)
|
|
38
36
|
|
|
@@ -51,12 +49,7 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
|
51
49
|
newman_cmd.add_argument(
|
|
52
50
|
"--reporters", action="store_true", default=False, required=False
|
|
53
51
|
)
|
|
54
|
-
newman_cmd.add_argument(
|
|
55
|
-
"--return-error",
|
|
56
|
-
choices=["all-findings", "errors-only", "never"],
|
|
57
|
-
default="never"
|
|
58
|
-
)
|
|
59
|
-
|
|
52
|
+
newman_cmd.add_argument("--severity-level", choices=["all", "medium", "high", "critical", "none"], default="none")
|
|
60
53
|
newman_cmd.print_usage = self.usage
|
|
61
54
|
newman_cmd.print_help = self.usage
|
|
62
55
|
return newman_cmd
|
|
@@ -113,7 +106,11 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
|
113
106
|
detach=True,
|
|
114
107
|
base_container=container,
|
|
115
108
|
use_native=args.use_docker_native)
|
|
109
|
+
|
|
110
|
+
newman_docker.prepare_client()
|
|
111
|
+
newman_docker.pre_run_validation(port)
|
|
116
112
|
newman_docker.run()
|
|
113
|
+
|
|
117
114
|
healthcheck = partial(
|
|
118
115
|
util.wait_for_healthcheck, "http://localhost:{}".format(port)
|
|
119
116
|
)
|
pyntcli/commands/postman.py
CHANGED
|
@@ -102,8 +102,10 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
|
|
|
102
102
|
detach=True,
|
|
103
103
|
base_container=container,
|
|
104
104
|
use_native=args.use_docker_native)
|
|
105
|
-
|
|
105
|
+
postman_docker.prepare_client()
|
|
106
|
+
postman_docker.pre_run_validation(args.port)
|
|
106
107
|
postman_docker.run()
|
|
108
|
+
|
|
107
109
|
ui_thread.print_generator(postman_docker.stdout)
|
|
108
110
|
|
|
109
111
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
pyntcli/commands/pynt_cmd.py
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import requests
|
|
2
3
|
from typing import Dict, List
|
|
4
|
+
from datetime import datetime, timedelta
|
|
3
5
|
from pyntcli import __version__ as cli_version
|
|
4
6
|
from pyntcli.analytics import send as analytics
|
|
5
7
|
from pyntcli.transport import pynt_requests
|
|
6
8
|
from pyntcli.ui import ui_thread
|
|
9
|
+
from pyntcli.ui import prompt
|
|
10
|
+
from pyntcli.store import CredStore, StateStore
|
|
11
|
+
import pyntcli.log.log as log
|
|
7
12
|
|
|
8
13
|
from requests.exceptions import SSLError, HTTPError
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
from . import command, listen, postman, root, sub_command, id_command, newman, har, burp
|
|
12
17
|
|
|
18
|
+
logger = log.get_logger()
|
|
19
|
+
|
|
13
20
|
avail_sub_commands = [
|
|
14
21
|
postman.PostmanSubCommand("postman"),
|
|
15
22
|
id_command.PyntShowIdCommand("pynt-id"),
|
|
@@ -20,11 +27,17 @@ avail_sub_commands = [
|
|
|
20
27
|
burp.BurpCommand("burp"),
|
|
21
28
|
]
|
|
22
29
|
|
|
30
|
+
commands_without_app_id = ["postman", "pynt-id"]
|
|
31
|
+
|
|
23
32
|
|
|
24
33
|
class PyntCommandException(Exception):
|
|
25
34
|
pass
|
|
26
35
|
|
|
27
36
|
|
|
37
|
+
class UserAbortedException(Exception):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
28
41
|
class BadArgumentsException(PyntCommandException):
|
|
29
42
|
pass
|
|
30
43
|
|
|
@@ -48,18 +61,22 @@ def check_is_latest_version(current_version):
|
|
|
48
61
|
ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
|
|
49
62
|
python3 -m pip install --upgrade pyntcli""", ui_thread.PrinterText.WARNING))
|
|
50
63
|
except SSLError:
|
|
51
|
-
ui_thread.print(ui_thread.PrinterText(
|
|
64
|
+
ui_thread.print(ui_thread.PrinterText(
|
|
65
|
+
"""Error: Unable to check if Pynt CLI version is up-to-date due to VPN/proxy. Run Pynt with --insecure to fix.""", ui_thread.PrinterText.WARNING))
|
|
52
66
|
except HTTPError:
|
|
53
|
-
ui_thread.print(
|
|
67
|
+
ui_thread.print(
|
|
68
|
+
"""Unable to check if Pynt CLI version is up-to-date""")
|
|
54
69
|
except Exception as e:
|
|
55
|
-
ui_thread.print(ui_thread.PrinterText(
|
|
70
|
+
ui_thread.print(ui_thread.PrinterText(
|
|
71
|
+
"""We could not check for updates.""", ui_thread.PrinterText.WARNING))
|
|
56
72
|
pass
|
|
57
73
|
|
|
58
74
|
|
|
59
75
|
class PyntCommand:
|
|
60
76
|
def __init__(self) -> None:
|
|
61
77
|
self.base: root.BaseCommand = root.BaseCommand()
|
|
62
|
-
self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {
|
|
78
|
+
self.sub_commands: Dict[str, sub_command.PyntSubCommand] = {
|
|
79
|
+
sc.get_name(): sc for sc in avail_sub_commands}
|
|
63
80
|
self._start_command()
|
|
64
81
|
|
|
65
82
|
def _start_command(self):
|
|
@@ -77,15 +94,93 @@ class PyntCommand:
|
|
|
77
94
|
command = getattr(args, "command")
|
|
78
95
|
if not command in self.sub_commands:
|
|
79
96
|
raise NoSuchCommandException()
|
|
80
|
-
|
|
97
|
+
check_is_latest_version(cli_version)
|
|
98
|
+
analytics.emit(analytics.CLI_START)
|
|
81
99
|
if "host_ca" in args and args.host_ca:
|
|
82
100
|
pynt_requests.add_host_ca(args.host_ca)
|
|
83
101
|
|
|
84
102
|
if "insecure" in args and args.insecure:
|
|
85
103
|
pynt_requests.disable_tls_termination()
|
|
86
104
|
|
|
87
|
-
check_is_latest_version(cli_version)
|
|
88
|
-
analytics.emit(analytics.CLI_START)
|
|
89
|
-
|
|
90
105
|
self.base.run_cmd(args)
|
|
106
|
+
|
|
107
|
+
# Some args are recommended/required for business-plan users.
|
|
108
|
+
# These can be verified only after running 'self.base.run_cmd()' where the login occurs
|
|
109
|
+
is_business_plan_user = self._is_business_plan_user()
|
|
110
|
+
try:
|
|
111
|
+
self._post_login_args_validation(
|
|
112
|
+
args, command, is_business_plan_user)
|
|
113
|
+
except UserAbortedException as e:
|
|
114
|
+
ui_thread.print("Aborting...")
|
|
115
|
+
return
|
|
116
|
+
|
|
91
117
|
self.sub_commands[command].run_cmd(args)
|
|
118
|
+
|
|
119
|
+
def _is_business_plan_user(self):
|
|
120
|
+
"""
|
|
121
|
+
A workaround for checking whether it's a free tier user or not
|
|
122
|
+
by try accessing api.pynt.io/v1/config (it's a free tier if the user can't access it)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool: True if it's a business-plan user
|
|
126
|
+
"""
|
|
127
|
+
saas_url = "https://api.pynt.io/v1/config"
|
|
128
|
+
headers = {}
|
|
129
|
+
with CredStore() as store:
|
|
130
|
+
access_keys = store.get_access_token()
|
|
131
|
+
token_type = store.get_token_type()
|
|
132
|
+
headers["Authorization"] = f"{token_type} {access_keys}"
|
|
133
|
+
try:
|
|
134
|
+
response = requests.get(saas_url, headers=headers, timeout=10)
|
|
135
|
+
|
|
136
|
+
if response.status_code == requests.codes.ok:
|
|
137
|
+
return True
|
|
138
|
+
else:
|
|
139
|
+
return False
|
|
140
|
+
except requests.exceptions.RequestException as e:
|
|
141
|
+
# Catch all other request exceptions
|
|
142
|
+
logger.debug(f"An error occurred when accessing '{saas_url}': {e}")
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
def _post_login_args_validation(self, args: argparse.Namespace, command: str, is_business_plan_user: bool):
|
|
146
|
+
# Confirm not using application-id flag if applicable
|
|
147
|
+
if getattr(args, "application_id") or command in commands_without_app_id:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# For a business user, it's recommended to always provide the application-id.
|
|
151
|
+
if is_business_plan_user:
|
|
152
|
+
if getattr(args, "yes") or self._is_missing_app_id_confirmed():
|
|
153
|
+
return
|
|
154
|
+
else:
|
|
155
|
+
raise UserAbortedException()
|
|
156
|
+
|
|
157
|
+
def _is_missing_app_id_confirmed(self) -> bool:
|
|
158
|
+
"""
|
|
159
|
+
Ask for the user's confirmation to continue if he/she wasn't asked in the last week.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: True if the user confirms or he/she has confirmed once in the last week. Otherwise, returns False
|
|
163
|
+
"""
|
|
164
|
+
with StateStore() as state_store:
|
|
165
|
+
current_time = datetime.now()
|
|
166
|
+
prompts_history = state_store.get_prompts_history()
|
|
167
|
+
last_confirmed = prompts_history.get(
|
|
168
|
+
"missing_app_id", {}).get("last_confirmation", "")
|
|
169
|
+
if last_confirmed:
|
|
170
|
+
# Calculate the time delta
|
|
171
|
+
parsed_datetime = datetime.strptime(
|
|
172
|
+
last_confirmed, "%Y-%m-%d %H:%M:%S")
|
|
173
|
+
difference = current_time - parsed_datetime
|
|
174
|
+
if difference < timedelta(days=7):
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
if prompt.confirmation_prompt_with_timeout("Application ID is missing. Use the '--application-id' flag to provide it.\n" +
|
|
178
|
+
"Without an Application ID, the scan will not be associated with your application.\n" +
|
|
179
|
+
"The Application ID can be fetched from https://app.pynt.io/dashboard/applications.\n" +
|
|
180
|
+
"Do you want to continue without associating the scan?", default="yes", timeout=15):
|
|
181
|
+
prompts_history["missing_app_id"] = {"last_confirmation": current_time.strftime("%Y-%m-%d %H:%M:%S")}
|
|
182
|
+
state_store.put_prompts_history(
|
|
183
|
+
prompts_history)
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
return False
|
pyntcli/commands/root.py
CHANGED
|
@@ -63,17 +63,27 @@ class BaseCommand:
|
|
|
63
63
|
action="store_true",
|
|
64
64
|
help="use when target uses self signed certificates",
|
|
65
65
|
)
|
|
66
|
-
parser.add_argument("--dev-flags", type=str,
|
|
66
|
+
parser.add_argument("--dev-flags", type=str,
|
|
67
|
+
default="", help=argparse.SUPPRESS)
|
|
67
68
|
parser.add_argument("--host-ca", type=str, default="")
|
|
68
69
|
parser.add_argument("--transport-config", type=str, default="")
|
|
69
|
-
parser.add_argument("--application-id", type=str,
|
|
70
|
+
parser.add_argument("--application-id", type=str,
|
|
71
|
+
default="", required=False)
|
|
70
72
|
parser.add_argument("--proxy", type=str, default="", required=False)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--yes",
|
|
75
|
+
"-y",
|
|
76
|
+
default=False,
|
|
77
|
+
required=False,
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Automatically answer yes to all prompts and run non-interactively ",
|
|
80
|
+
)
|
|
71
81
|
parser.add_argument(
|
|
72
82
|
"--verbose",
|
|
73
83
|
default=False,
|
|
74
84
|
required=False,
|
|
75
85
|
action="store_true",
|
|
76
|
-
help="
|
|
86
|
+
help="Use to get more detailed execution information",
|
|
77
87
|
)
|
|
78
88
|
parser.add_argument(
|
|
79
89
|
"--use-docker-native",
|
pyntcli/commands/util.py
CHANGED
|
@@ -68,6 +68,10 @@ class SomeFindingsOrWarningsException(Exception):
|
|
|
68
68
|
pass
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
class SeverityException(Exception):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
71
75
|
@contextmanager
|
|
72
76
|
def create_default_file_mounts(args):
|
|
73
77
|
html_report_path = os.path.join(tempfile.gettempdir(), "results.html")
|
|
@@ -93,8 +97,10 @@ def create_default_file_mounts(args):
|
|
|
93
97
|
report.PyntReporter(json_report_path).print_summary()
|
|
94
98
|
|
|
95
99
|
check_for_findings_or_warnings(args, json.load(open(json_report_path)))
|
|
100
|
+
check_severity(args.severity_level, json.load(open(json_report_path)))
|
|
96
101
|
|
|
97
102
|
|
|
103
|
+
# Deprecate - keep it for backward customers that use it
|
|
98
104
|
def check_for_findings_or_warnings(args, json_report):
|
|
99
105
|
security_tests = json_report.get("securityTests", {})
|
|
100
106
|
findings = security_tests.get("Findings", 0)
|
|
@@ -105,3 +111,30 @@ def check_for_findings_or_warnings(args, json_report):
|
|
|
105
111
|
|
|
106
112
|
if "return_error" in args and args.return_error == "all-findings" and warnings != 0:
|
|
107
113
|
raise SomeFindingsOrWarningsException()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check_severity(severity_flag, json_report):
|
|
117
|
+
severity_levels = ['medium', 'high', 'critical']
|
|
118
|
+
|
|
119
|
+
if severity_flag is None or severity_flag.lower() == "none":
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
risk_data = json_report.get("securityTests", {})
|
|
123
|
+
|
|
124
|
+
# Normalize all te keys to lower to reduce the risk that user input is diff then what we have internally
|
|
125
|
+
risks_counter = {key.lower(): value for key, value in risk_data['RisksCounter'].items()}
|
|
126
|
+
|
|
127
|
+
severity_filter = severity_flag.lower()
|
|
128
|
+
|
|
129
|
+
if severity_filter == "all":
|
|
130
|
+
severities_to_check = severity_levels
|
|
131
|
+
else:
|
|
132
|
+
if severity_filter not in severity_levels:
|
|
133
|
+
raise ValueError(f"Invalid user filter for severity level: {severity_filter}")
|
|
134
|
+
|
|
135
|
+
flag_index = severity_levels.index(severity_filter)
|
|
136
|
+
severities_to_check = severity_levels[flag_index:]
|
|
137
|
+
|
|
138
|
+
for severity in severities_to_check:
|
|
139
|
+
if risks_counter.get(severity, 0) > 0:
|
|
140
|
+
raise SeverityException(f"Severity '{severity}' has a count greater than 0.")
|
pyntcli/main.py
CHANGED
|
@@ -12,7 +12,7 @@ from requests.exceptions import SSLError
|
|
|
12
12
|
from requests.exceptions import ProxyError
|
|
13
13
|
from pyntcli.transport.pynt_requests import InvalidPathException, InvalidCertFormat
|
|
14
14
|
from pyntcli.commands.util import HtmlReportNotCreatedException
|
|
15
|
-
from pyntcli.commands.util import SomeFindingsOrWarningsException
|
|
15
|
+
from pyntcli.commands.util import SomeFindingsOrWarningsException, SeverityException
|
|
16
16
|
from pyntcli.commands.postman import PyntWebSocketException
|
|
17
17
|
from pyntcli import __version__
|
|
18
18
|
|
|
@@ -131,6 +131,8 @@ def main():
|
|
|
131
131
|
analytics.emit(analytics.ERROR, {"error": "port in use. e: {}".format(e)})
|
|
132
132
|
except SomeFindingsOrWarningsException as e:
|
|
133
133
|
exit(1)
|
|
134
|
+
except SeverityException as e:
|
|
135
|
+
exit(1)
|
|
134
136
|
except Exception as e:
|
|
135
137
|
analytics.emit(analytics.ERROR, {"error": "{}".format(e)})
|
|
136
138
|
pynt_errors.unexpected_error(e)
|
|
@@ -2,6 +2,7 @@ import platform
|
|
|
2
2
|
import subprocess
|
|
3
3
|
import docker
|
|
4
4
|
from docker.errors import DockerException, APIError, ImageNotFound
|
|
5
|
+
from requests.exceptions import ReadTimeout
|
|
5
6
|
from docker.types import Mount
|
|
6
7
|
import os
|
|
7
8
|
import json
|
|
@@ -17,6 +18,9 @@ from pyntcli.store import CredStore
|
|
|
17
18
|
from pyntcli.auth.login import PYNT_ID, PYNT_SAAS, PYNT_BUCKET_NAME, PYNT_PARAM1, PYNT_PARAM2
|
|
18
19
|
|
|
19
20
|
PYNT_DOCKER_IMAGE = "ghcr.io/pynt-io/pynt"
|
|
21
|
+
IMAGE_TAGS = ["postman-latest", "newman-latest", "har-latest", "proxy-latest"]
|
|
22
|
+
|
|
23
|
+
PYNT_CONTAINER_INTERNAL_PORT = "5001"
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
def create_mount(src, destination, mount_type="bind"):
|
|
@@ -37,7 +41,8 @@ class ImageUnavailableException(Exception):
|
|
|
37
41
|
|
|
38
42
|
class PortInUseException(Exception):
|
|
39
43
|
def __init__(self, port=""):
|
|
40
|
-
self.message = ui_thread.print(
|
|
44
|
+
self.message = ui_thread.print(
|
|
45
|
+
ui_thread.PrinterText(f"Port: {port} already in use, please use a different one", ui_thread.PrinterText.WARNING))
|
|
41
46
|
super().__init__(self.message)
|
|
42
47
|
|
|
43
48
|
|
|
@@ -87,8 +92,6 @@ def get_container_with_arguments(args: argparse.Namespace, *port_args: PyntDocke
|
|
|
87
92
|
docker_arguments = []
|
|
88
93
|
ports = {}
|
|
89
94
|
for p in port_args:
|
|
90
|
-
if container_utils.is_port_in_use(p.dest):
|
|
91
|
-
raise PortInUseException(p.dest)
|
|
92
95
|
if "desktop" in get_docker_type().lower():
|
|
93
96
|
ports[str(p.src)] = int(p.dest)
|
|
94
97
|
else:
|
|
@@ -189,9 +192,11 @@ class PyntContainerNative:
|
|
|
189
192
|
|
|
190
193
|
return len(result.stdout.splitlines()) > 1
|
|
191
194
|
|
|
195
|
+
def prepare_client(self):
|
|
196
|
+
pass
|
|
197
|
+
|
|
192
198
|
def run(self):
|
|
193
199
|
self.running = True
|
|
194
|
-
self.kill_other_instances()
|
|
195
200
|
|
|
196
201
|
self.get_image()
|
|
197
202
|
args = self.base_container.docker_arguments if self.base_container.docker_arguments else None
|
|
@@ -247,18 +252,22 @@ class PyntContainerNative:
|
|
|
247
252
|
|
|
248
253
|
self.stdout = logs_stdout
|
|
249
254
|
|
|
250
|
-
def kill_other_instances(self):
|
|
255
|
+
def kill_other_instances(self, report_to_user=True):
|
|
256
|
+
ui_thread.print_verbose("Killing other pynt containers if such exist")
|
|
251
257
|
try:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
258
|
+
for tag in IMAGE_TAGS:
|
|
259
|
+
command = ["docker", "ps", "-q", "-f", f"ancestor={self.image_name}:{tag}"]
|
|
260
|
+
containers_output = subprocess.check_output(command, text=True)
|
|
261
|
+
if not containers_output:
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
container_ids = containers_output.splitlines()
|
|
265
|
+
for container_id in container_ids:
|
|
266
|
+
command = ["docker", "kill", container_id]
|
|
267
|
+
subprocess.run(command)
|
|
268
|
+
if report_to_user:
|
|
269
|
+
ui_thread.print(
|
|
270
|
+
ui_thread.PrinterText("Another Pynt container was running, killed it", ui_thread.PrinterText))
|
|
262
271
|
|
|
263
272
|
except subprocess.CalledProcessError:
|
|
264
273
|
analytics.emit(analytics.ERROR, {"error": "Unable to kill other pynt containers"})
|
|
@@ -305,7 +314,7 @@ class PyntContainerNative:
|
|
|
305
314
|
def stop(self):
|
|
306
315
|
if not self.running:
|
|
307
316
|
return
|
|
308
|
-
self.kill_other_instances()
|
|
317
|
+
self.kill_other_instances(report_to_user=False)
|
|
309
318
|
self.running = False
|
|
310
319
|
|
|
311
320
|
def adapt_run_command(self, docker_command=[]):
|
|
@@ -333,6 +342,7 @@ class PyntContainerSDK:
|
|
|
333
342
|
self.container_name = ""
|
|
334
343
|
self.stdout = None
|
|
335
344
|
self.running = False
|
|
345
|
+
self.system = platform.system().lower()
|
|
336
346
|
|
|
337
347
|
def _initialize(self):
|
|
338
348
|
self.docker_client = docker.from_env()
|
|
@@ -352,15 +362,16 @@ class PyntContainerSDK:
|
|
|
352
362
|
|
|
353
363
|
return l[0].status == "running"
|
|
354
364
|
|
|
365
|
+
def prepare_client(self):
|
|
366
|
+
if not self.docker_client:
|
|
367
|
+
self._initialize()
|
|
368
|
+
|
|
355
369
|
def run(self):
|
|
356
370
|
if not self.docker_client:
|
|
357
371
|
self._initialize()
|
|
358
372
|
|
|
359
373
|
self.running = True
|
|
360
374
|
|
|
361
|
-
ui_thread.print_verbose("Killing other pynt containers if such exist")
|
|
362
|
-
self.kill_other_instances()
|
|
363
|
-
|
|
364
375
|
image = self.get_image()
|
|
365
376
|
ui_thread.print(ui_thread.PrinterText("Docker pull done", ui_thread.PrinterText.INFO))
|
|
366
377
|
|
|
@@ -383,10 +394,25 @@ class PyntContainerSDK:
|
|
|
383
394
|
self.container_name = c.name
|
|
384
395
|
self.stdout = c.logs(stream=True)
|
|
385
396
|
|
|
386
|
-
def kill_other_instances(self):
|
|
397
|
+
def kill_other_instances(self, report_to_user=True):
|
|
387
398
|
for c in self.docker_client.containers.list():
|
|
388
399
|
if len(c.image.tags) and _container_image_from_tag(c.image.tags[0]) == self.image_name:
|
|
389
400
|
c.kill()
|
|
401
|
+
self.wait_for_container_end(c)
|
|
402
|
+
if report_to_user:
|
|
403
|
+
ui_thread.print(ui_thread.PrinterText("Another Pynt container was running, killed it", ui_thread.PrinterText))
|
|
404
|
+
|
|
405
|
+
def wait_for_container_end(self, container):
|
|
406
|
+
# only windows kill is require a wait for the container to stop, otherwise the port stays in use
|
|
407
|
+
if self.system != "windows":
|
|
408
|
+
return
|
|
409
|
+
try:
|
|
410
|
+
container.wait(timeout=10)
|
|
411
|
+
except ReadTimeout: # container is still running
|
|
412
|
+
ui_thread.print(
|
|
413
|
+
ui_thread.PrinterText("Timeout reached while waiting for container to stop", ui_thread.PrinterText))
|
|
414
|
+
except APIError: # container is already stopped
|
|
415
|
+
pass
|
|
390
416
|
|
|
391
417
|
def pull_image(self):
|
|
392
418
|
try:
|
|
@@ -410,7 +436,7 @@ class PyntContainerSDK:
|
|
|
410
436
|
def stop(self):
|
|
411
437
|
if not self.running:
|
|
412
438
|
return
|
|
413
|
-
self.kill_other_instances()
|
|
439
|
+
self.kill_other_instances(report_to_user=False)
|
|
414
440
|
self.docker_client.close()
|
|
415
441
|
self.docker_client = None
|
|
416
442
|
self.running = False
|
|
@@ -452,6 +478,15 @@ class PyntContainer:
|
|
|
452
478
|
self.stdout = self.client_implementation.stdout
|
|
453
479
|
PyntContainerRegistry.instance().register_container(self)
|
|
454
480
|
|
|
481
|
+
def pre_run_validation(self, port):
|
|
482
|
+
self.kill_other_instances()
|
|
483
|
+
|
|
484
|
+
if container_utils.is_port_in_use(int(port)):
|
|
485
|
+
raise PortInUseException(port)
|
|
486
|
+
|
|
487
|
+
def prepare_client(self):
|
|
488
|
+
self.client_implementation.prepare_client()
|
|
489
|
+
|
|
455
490
|
def running(self):
|
|
456
491
|
return self.client_implementation.running
|
|
457
492
|
|
pyntcli/store/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from .store import CredStore
|
|
1
|
+
from .store import CredStore, StateStore
|
pyntcli/store/store.py
CHANGED
|
@@ -22,7 +22,8 @@ class Store():
|
|
|
22
22
|
|
|
23
23
|
if not os.path.exists(self.file_location):
|
|
24
24
|
with open(self.file_location, "w") as f:
|
|
25
|
-
self.connector = self._connector_type(
|
|
25
|
+
self.connector = self._connector_type(
|
|
26
|
+
self._connector_type.default_value())
|
|
26
27
|
return
|
|
27
28
|
|
|
28
29
|
with open(self.file_location, "r+") as f:
|
|
@@ -39,6 +40,9 @@ class Store():
|
|
|
39
40
|
def get_path(self):
|
|
40
41
|
return self.file_location
|
|
41
42
|
|
|
43
|
+
def _get_default_store_dir(self):
|
|
44
|
+
return os.path.join(os.path.expanduser("~"), ".pynt")
|
|
45
|
+
|
|
42
46
|
def __enter__(self):
|
|
43
47
|
self._get_file_data()
|
|
44
48
|
return self
|
|
@@ -50,14 +54,31 @@ class Store():
|
|
|
50
54
|
|
|
51
55
|
class CredStore(Store):
|
|
52
56
|
def __init__(self) -> None:
|
|
53
|
-
|
|
54
|
-
super().__init__(file_location=os.path.join(
|
|
57
|
+
pynt_dir = super()._get_default_store_dir()
|
|
58
|
+
super().__init__(file_location=os.path.join(pynt_dir, "creds.json"),
|
|
55
59
|
connector_type=JsonStoreConnector)
|
|
56
60
|
|
|
57
61
|
def get_access_token(self):
|
|
58
62
|
return self.get("token")["access_token"]
|
|
59
63
|
|
|
64
|
+
def get_token_type(self):
|
|
65
|
+
return self.get("token")["token_type"]
|
|
66
|
+
|
|
60
67
|
def get_tokens(self):
|
|
61
68
|
all_tokens = self.get("token")
|
|
62
|
-
token_to_json_string = '{"token":' +
|
|
69
|
+
token_to_json_string = '{"token":' + \
|
|
70
|
+
str(all_tokens).replace("\'", "\"") + "}"
|
|
63
71
|
return token_to_json_string
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class StateStore(Store):
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
pynt_dir = super()._get_default_store_dir()
|
|
77
|
+
super().__init__(file_location=os.path.join(pynt_dir, "state.json"),
|
|
78
|
+
connector_type=JsonStoreConnector)
|
|
79
|
+
|
|
80
|
+
def get_prompts_history(self):
|
|
81
|
+
return self.get("prompts_history") or {}
|
|
82
|
+
|
|
83
|
+
def put_prompts_history(self, value):
|
|
84
|
+
return self.put("prompts_history", value)
|
pyntcli/ui/prompt.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import signal
|
|
2
|
+
from pyntcli.ui import ui_thread
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TimeoutExpired(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _timeout_handler(signum, frame):
|
|
10
|
+
raise TimeoutExpired
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def confirmation_prompt_with_timeout(question, default, timeout=10):
|
|
14
|
+
"""Prompt the user with a confirmation question (Yes/No) and return True for 'yes' or False for 'no'.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
question (str): The question to present to the user.
|
|
18
|
+
default (str): The default answer if the user just presses Enter. It should be 'yes' or 'no'.
|
|
19
|
+
timeout (int): The timeout in seconds. If the user doesn't respond within the timeout, the default answer will be selected.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: True if the answer is 'yes', False if the answer is 'no'.
|
|
23
|
+
"""
|
|
24
|
+
valid = {"yes": True, "y": True, "no": False, "n": False}
|
|
25
|
+
|
|
26
|
+
if default is None:
|
|
27
|
+
prompt = " [y/n] "
|
|
28
|
+
elif default == "yes":
|
|
29
|
+
prompt = " [Y/n] "
|
|
30
|
+
elif default == "no":
|
|
31
|
+
prompt = " [y/N] "
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError(f"Invalid default answer: '{default}'")
|
|
34
|
+
|
|
35
|
+
signal.signal(signal.SIGALRM, _timeout_handler)
|
|
36
|
+
signal.alarm(timeout)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
while True:
|
|
40
|
+
choice = input(question + prompt).strip().lower()
|
|
41
|
+
|
|
42
|
+
if choice == "" and default: # Only 'Enter' with default will continue
|
|
43
|
+
return valid[default]
|
|
44
|
+
elif choice in valid:
|
|
45
|
+
return valid[choice]
|
|
46
|
+
else:
|
|
47
|
+
ui_thread.print("Please respond with 'yes' or 'no' (or 'y' or 'n').")
|
|
48
|
+
except TimeoutExpired:
|
|
49
|
+
ui_thread.print(ui_thread.PrinterText(f"\nNo response within {timeout} seconds, defaulting to {'Yes' if default == 'yes' else 'No'}.\n" +
|
|
50
|
+
"You can use '--yes' flag for auto-confirm", ui_thread.PrinterText.WARNING))
|
|
51
|
+
return valid[default]
|
|
52
|
+
finally:
|
|
53
|
+
signal.alarm(0)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyntcli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.94
|
|
4
4
|
Summary: Command line utility to handle all of Pynt's different integrations
|
|
5
5
|
Author-email: Pynt-io <support@pynt.io>
|
|
6
6
|
Project-URL: Homepage, https://pynt.io
|
|
7
7
|
Requires-Python: >=3.7
|
|
8
8
|
Requires-Dist: docker
|
|
9
9
|
Requires-Dist: rich
|
|
10
|
-
Requires-Dist: requests
|
|
10
|
+
Requires-Dist: requests==2.31.0
|
|
11
11
|
Requires-Dist: pem
|
|
12
|
-
Requires-Dist: certifi
|
|
13
|
-
Requires-Dist: logzio-python-handler
|
|
12
|
+
Requires-Dist: certifi>=2017.4.17
|
|
13
|
+
Requires-Dist: logzio-python-handler>=4.1.0
|
|
14
14
|
Requires-Dist: websocket-client
|
|
15
15
|
Requires-Dist: xmltodict
|
|
16
16
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
ignoreTests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
|
|
2
|
+
ignoreTests/auth/login.py,sha256=KFlzWhXBAuwdi7GXf16gCB3ya94LQG2wjcSChE149rQ,3798
|
|
3
|
+
ignoreTests/store/cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
|
|
4
|
+
pyntcli/__init__.py,sha256=5NPBuxeQo_sfTOMuw7XfyXg8HYg3BMvDd69Cm_HznpU,402
|
|
5
|
+
pyntcli/main.py,sha256=HORWY5AEK5xmEQybUZBW9ZEDIga8vSnqDfmAR3TY9F0,6502
|
|
6
|
+
pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pyntcli/analytics/send.py,sha256=9TRAEoPbv4rWOZfcNaGanrRJAFvNs39P-uKSl49GcQE,3679
|
|
8
|
+
pyntcli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pyntcli/auth/login.py,sha256=TljsRXbEkNI1YUrKm5mlTw4YiecYScYUsit8Z8vstss,5228
|
|
10
|
+
pyntcli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
pyntcli/commands/burp.py,sha256=TYUspSLhubtKEL4ShUOvT7Fnq3MFsFo-HyrPXiZboZ4,12380
|
|
12
|
+
pyntcli/commands/command.py,sha256=cMVrlxMZgx0jFrlpMTKPEYfsSv2RhN6XuPrRqqqYB6k,11125
|
|
13
|
+
pyntcli/commands/har.py,sha256=yUuLvxlAoq2dQotJe8PV02sZD-WgYSefzGc6bhdroSo,4464
|
|
14
|
+
pyntcli/commands/id_command.py,sha256=UBEgMIpm4vauTCsKyixltiGUolNg_OfHEJvJ_i5BpJY,943
|
|
15
|
+
pyntcli/commands/listen.py,sha256=b_ES6VRmPFkIkCCQ6MuP6KOeUlf5vT1e7AafnDqJVak,9063
|
|
16
|
+
pyntcli/commands/newman.py,sha256=o3kobgUFoC3jw2XmbYEsQNdYpGO5suFA92lxHw71dzw,5301
|
|
17
|
+
pyntcli/commands/postman.py,sha256=VNARuyCDaDYlrzRMmJk4oM4YTCExOQUDziOS8NpU-KA,5256
|
|
18
|
+
pyntcli/commands/pynt_cmd.py,sha256=T7jee0yw67Zth3lTkSDInCLnbu_IhpNqb7GqnTiTceQ,7012
|
|
19
|
+
pyntcli/commands/root.py,sha256=4yeGlTeL3Xu2xlSbMYhhaDwUnP29sF2Y2sxq8E1P3lo,3986
|
|
20
|
+
pyntcli/commands/static_file_extensions.py,sha256=PZJb02BI-64tbU-j3rdCNsXzTh7gkIDGxGKbKNw3h5k,1995
|
|
21
|
+
pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
|
|
22
|
+
pyntcli/commands/util.py,sha256=csZHQ2Xbdh-_KX-yIVrnaeNsT0NbuS-ej6kND3CxD_w,4414
|
|
23
|
+
pyntcli/log/__init__.py,sha256=cOGwOYzMoshEbZiiasBGkj6wF0SBu3Jdpl-AuakDesw,19
|
|
24
|
+
pyntcli/log/log.py,sha256=cWCdWmUaAwePwdhYDcgNMEG9d9RM34sGahxBCYEdv2Y,1069
|
|
25
|
+
pyntcli/pynt_docker/__init__.py,sha256=PQIOVxc7XXtMLfEX7ojgwf_Z3mmTllO3ZvzUZTPOxQY,30
|
|
26
|
+
pyntcli/pynt_docker/container_utils.py,sha256=_Onn7loInzyJAG2-Uk6CGpsuRyelmUFHOvtJj4Uzi9A,175
|
|
27
|
+
pyntcli/pynt_docker/pynt_container.py,sha256=R2iq4Gbbkxg3OEJfJwUp-vroHf9-pJ6HWFqvWucPsGA,18965
|
|
28
|
+
pyntcli/store/__init__.py,sha256=1fP8cEAQCF_myja3gnhHH9FEqtBiOJ-2aBmUXSKBdFA,41
|
|
29
|
+
pyntcli/store/json_connector.py,sha256=UGs3uORw3iyn0YJ8kzab-veEZToA6d-ByXYuqEleWsA,560
|
|
30
|
+
pyntcli/store/store.py,sha256=ZLSe0WAjHDp8cSt4BBFDkPGRux4cgOo5UfF7V4naM7U,2559
|
|
31
|
+
pyntcli/store/store_connector.py,sha256=w4LzcpRZesUZL1f63RmLlWEFRtJ6Y6rcS6PkkGtO4MA,357
|
|
32
|
+
pyntcli/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
pyntcli/transport/pynt_requests.py,sha256=C7OPvcKkRTcxSYuyiWKE59KgA9sRX0d6fm1wnopAmPo,1719
|
|
34
|
+
pyntcli/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
pyntcli/ui/progress.py,sha256=RrnO_jJNunoyupylakmWmHOEPw3lh99OHpKBzL6OBiE,1008
|
|
36
|
+
pyntcli/ui/prompt.py,sha256=6URUWCGpKUPhluWTYVaXvCQuABjiFfJSE3rfRJMvAS0,1838
|
|
37
|
+
pyntcli/ui/pynt_errors.py,sha256=00UprD4tFViREv7kuXGQ99PAKGTpXYixxi3Ndeoeiew,689
|
|
38
|
+
pyntcli/ui/report.py,sha256=W-icPSZrGLOubXgam0LpOvHLl_aZg9Zx9qIkL8Ym5PE,1930
|
|
39
|
+
pyntcli/ui/ui_thread.py,sha256=XUBgLpYQjVhrilU-ofw7VSXgTiwneSdTxm61EvC3x4Q,5091
|
|
40
|
+
tests/test_utils.py,sha256=t5fTQUk1U_Js6iMxcGYGqt4C-crzOJ0CqCKtLkRtUi0,2050
|
|
41
|
+
tests/commands/test_pynt_cmd.py,sha256=BjGFCFACcSziLrNA6_27t6TjSmvdu54wx9njwLpRSJY,8379
|
|
42
|
+
pyntcli-0.1.94.dist-info/METADATA,sha256=m__wWpEYNkwzQMqrCy14feFm2kT8jxPRamGVZSk1cJ4,460
|
|
43
|
+
pyntcli-0.1.94.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
44
|
+
pyntcli-0.1.94.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
|
|
45
|
+
pyntcli-0.1.94.dist-info/top_level.txt,sha256=64XSgBzSpgwjYjEKHZE7q3JH2a816zEeyZBXfJi3AKI,42
|
|
46
|
+
pyntcli-0.1.94.dist-info/RECORD,,
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import argparse
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from unittest.mock import patch, MagicMock
|
|
5
|
+
from pyntcli.commands.pynt_cmd import PyntCommand, UserAbortedException
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestIsBusinessPlanUser(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
12
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
13
|
+
def test_is_business_plan_user_success(self, mock_cred_store, mock_requests_get):
|
|
14
|
+
# Mock the CredStore context manager
|
|
15
|
+
mock_store_instance = MagicMock()
|
|
16
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
17
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
18
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
19
|
+
|
|
20
|
+
# Mock the requests.get response
|
|
21
|
+
mock_response = MagicMock()
|
|
22
|
+
mock_response.status_code = 200
|
|
23
|
+
mock_requests_get.return_value = mock_response
|
|
24
|
+
|
|
25
|
+
# Instantiate the class and call the method
|
|
26
|
+
instance = PyntCommand()
|
|
27
|
+
result = instance._is_business_plan_user()
|
|
28
|
+
|
|
29
|
+
# Assert the result
|
|
30
|
+
self.assertTrue(result)
|
|
31
|
+
|
|
32
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
33
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
34
|
+
def test_is_business_plan_user_failure(self, mock_cred_store, mock_requests_get):
|
|
35
|
+
# Mock the CredStore context manager
|
|
36
|
+
mock_store_instance = MagicMock()
|
|
37
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
38
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
39
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
40
|
+
|
|
41
|
+
# Mock the requests.get response
|
|
42
|
+
mock_response = MagicMock()
|
|
43
|
+
mock_response.status_code = 403
|
|
44
|
+
mock_requests_get.return_value = mock_response
|
|
45
|
+
|
|
46
|
+
# Instantiate the class and call the method
|
|
47
|
+
instance = PyntCommand()
|
|
48
|
+
result = instance._is_business_plan_user()
|
|
49
|
+
|
|
50
|
+
# Assert the result
|
|
51
|
+
self.assertFalse(result)
|
|
52
|
+
|
|
53
|
+
@patch('pyntcli.commands.pynt_cmd.requests.get')
|
|
54
|
+
@patch('pyntcli.commands.pynt_cmd.CredStore')
|
|
55
|
+
def test_is_business_plan_user_exception(self, mock_cred_store, mock_requests_get):
|
|
56
|
+
# Mock the CredStore context manager
|
|
57
|
+
mock_store_instance = MagicMock()
|
|
58
|
+
mock_store_instance.get_access_token.return_value = 'fake_token'
|
|
59
|
+
mock_store_instance.get_token_type.return_value = 'Bearer'
|
|
60
|
+
mock_cred_store.return_value.__enter__.return_value = mock_store_instance
|
|
61
|
+
|
|
62
|
+
# Mock the requests.get to raise an exception
|
|
63
|
+
mock_requests_get.side_effect = requests.exceptions.RequestException
|
|
64
|
+
|
|
65
|
+
# Instantiate the class and call the method
|
|
66
|
+
instance = PyntCommand()
|
|
67
|
+
result = instance._is_business_plan_user()
|
|
68
|
+
|
|
69
|
+
# Assert the result
|
|
70
|
+
self.assertFalse(result)
|
|
71
|
+
|
|
72
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=True)
|
|
73
|
+
def test_post_login_args_validation_missing_app_id_user_confirmed(self, _):
|
|
74
|
+
# Instantiate the class and call the method with valid arguments
|
|
75
|
+
instance = PyntCommand()
|
|
76
|
+
try:
|
|
77
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
78
|
+
application_id="", yes=False), "non-postman-command", True)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
81
|
+
|
|
82
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
83
|
+
def test_post_login_args_validation_missing_app_id_yes_flag_used(self, _):
|
|
84
|
+
# Instantiate the class and call the method with valid arguments
|
|
85
|
+
instance = PyntCommand()
|
|
86
|
+
try:
|
|
87
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
88
|
+
application_id="", yes=True), "non-postman-command", True)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
91
|
+
|
|
92
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
93
|
+
def test_post_login_args_validation_missing_app_id_user_aborted(self, _):
|
|
94
|
+
# Instantiate the class and call the method with valid arguments
|
|
95
|
+
instance = PyntCommand()
|
|
96
|
+
with self.assertRaises(UserAbortedException):
|
|
97
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
98
|
+
application_id="", yes=False), "non-postman-command", True)
|
|
99
|
+
|
|
100
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
101
|
+
def test_post_login_args_validation_missing_app_id_free_tier(self, _):
|
|
102
|
+
# Instantiate the class and call the method with valid arguments
|
|
103
|
+
instance = PyntCommand()
|
|
104
|
+
try:
|
|
105
|
+
result = instance._post_login_args_validation(argparse.Namespace(
|
|
106
|
+
application_id="", yes=False), "non-postman-command", False)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
109
|
+
|
|
110
|
+
@patch.object(PyntCommand, '_is_missing_app_id_confirmed', return_value=False)
|
|
111
|
+
def test_post_login_args_validation_missing_app_id_not_required(self, _):
|
|
112
|
+
# Instantiate the class and call the method with valid arguments
|
|
113
|
+
instance = PyntCommand()
|
|
114
|
+
try:
|
|
115
|
+
result = instance._post_login_args_validation(
|
|
116
|
+
argparse.Namespace(application_id="", yes=False), "postman", True)
|
|
117
|
+
result = instance._post_login_args_validation(
|
|
118
|
+
argparse.Namespace(application_id="", yes=False), "pynt-id", True)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
self.fail(f"Unexpected exception raised {e}")
|
|
121
|
+
|
|
122
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=True)
|
|
123
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
124
|
+
def test_missing_app_id_confirmation_confirmation_expired_user_confirms(self, mock_state_store, _):
|
|
125
|
+
long_time_ago = datetime.now() - timedelta(days=100)
|
|
126
|
+
# Mock the CredStore context manager
|
|
127
|
+
mock_store_instance = MagicMock()
|
|
128
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
129
|
+
"last_confirmation": long_time_ago.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
130
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
131
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
132
|
+
|
|
133
|
+
# Instantiate the class and call the method with app_id present
|
|
134
|
+
instance = PyntCommand()
|
|
135
|
+
result = instance._is_missing_app_id_confirmed()
|
|
136
|
+
|
|
137
|
+
# Assert the result
|
|
138
|
+
self.assertTrue(result)
|
|
139
|
+
|
|
140
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=False)
|
|
141
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
142
|
+
def test_missing_app_id_confirmation_confirmation_expired_user_aborts(self, mock_state_store, _):
|
|
143
|
+
long_time_ago = datetime.now() - timedelta(days=100)
|
|
144
|
+
# Mock the CredStore context manager
|
|
145
|
+
mock_store_instance = MagicMock()
|
|
146
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
147
|
+
"last_confirmation": long_time_ago.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
148
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
149
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
150
|
+
|
|
151
|
+
# Instantiate the class and call the method with app_id present
|
|
152
|
+
instance = PyntCommand()
|
|
153
|
+
result = instance._is_missing_app_id_confirmed()
|
|
154
|
+
|
|
155
|
+
# Assert the result
|
|
156
|
+
self.assertFalse(result)
|
|
157
|
+
|
|
158
|
+
@patch('pyntcli.commands.pynt_cmd.prompt.confirmation_prompt_with_timeout', return_value=False)
|
|
159
|
+
@patch('pyntcli.commands.pynt_cmd.StateStore')
|
|
160
|
+
def test_missing_app_id_confirmation_user_already_confirmed(self, mock_state_store, _):
|
|
161
|
+
recently = datetime.now() - timedelta(hours=1)
|
|
162
|
+
# Mock the CredStore context manager
|
|
163
|
+
mock_store_instance = MagicMock()
|
|
164
|
+
mock_store_instance.get_prompts_history.return_value = {"missing_app_id": {
|
|
165
|
+
"last_confirmation": recently.strftime("%Y-%m-%d %H:%M:%S")}}
|
|
166
|
+
mock_store_instance.put_prompts_history.return_value = None
|
|
167
|
+
mock_state_store.return_value.__enter__.return_value = mock_store_instance
|
|
168
|
+
|
|
169
|
+
# Instantiate the class and call the method with app_id present
|
|
170
|
+
instance = PyntCommand()
|
|
171
|
+
result = instance._is_missing_app_id_confirmed()
|
|
172
|
+
|
|
173
|
+
# Assert the result
|
|
174
|
+
self.assertTrue(result)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == '__main__':
|
|
178
|
+
unittest.main()
|
tests/test_utils.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pyntcli.commands.util import check_severity, SeverityException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.fixture
|
|
6
|
+
def risk_data():
|
|
7
|
+
return {
|
|
8
|
+
"securityTests": {
|
|
9
|
+
'DidNotRun': 0,
|
|
10
|
+
'Duration': 0,
|
|
11
|
+
'Findings': 5,
|
|
12
|
+
'Passed': 27,
|
|
13
|
+
'RisksCounter': {'Critical': 2, 'High': 0, 'Low': 3, 'Medium': 3},
|
|
14
|
+
'Warnings': 1
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_exception_on_medium_severity(risk_data):
|
|
20
|
+
with pytest.raises(SeverityException):
|
|
21
|
+
check_severity('MEDIUM', risk_data)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_exception_on_high_severity(risk_data):
|
|
25
|
+
with pytest.raises(SeverityException):
|
|
26
|
+
check_severity('HIGH', risk_data)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_exception_on_critical_severity(risk_data):
|
|
30
|
+
with pytest.raises(SeverityException):
|
|
31
|
+
check_severity('CRITICAL', risk_data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_no_exception_on_higher_than_all_severity():
|
|
35
|
+
data_with_low_risk_only = {
|
|
36
|
+
"securityTests": {
|
|
37
|
+
'DidNotRun': 0,
|
|
38
|
+
'Duration': 0,
|
|
39
|
+
'Findings': 5,
|
|
40
|
+
'Passed': 27,
|
|
41
|
+
'RisksCounter': {'Critical': 0, 'High': 0, 'Low': 3, 'Medium': 0},
|
|
42
|
+
'Warnings': 1
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try:
|
|
46
|
+
check_severity('HIGH', data_with_low_risk_only)
|
|
47
|
+
except SeverityException:
|
|
48
|
+
pytest.fail("SeverityException was raised unexpectedly!")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_invalid_severity_flag(risk_data):
|
|
52
|
+
with pytest.raises(ValueError):
|
|
53
|
+
check_severity('invalid', risk_data)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_exception_on_all_flag(risk_data):
|
|
57
|
+
with pytest.raises(SeverityException):
|
|
58
|
+
check_severity('ALL', risk_data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_no_exception_on_all_flag_when_empty():
|
|
62
|
+
data_with_no_risks = {
|
|
63
|
+
"securityTests": {
|
|
64
|
+
'DidNotRun': 0,
|
|
65
|
+
'Duration': 0,
|
|
66
|
+
'Findings': 5,
|
|
67
|
+
'Passed': 27,
|
|
68
|
+
'RisksCounter': {'Critical': 0, 'High': 0, 'Low': 0, 'Medium': 0},
|
|
69
|
+
'Warnings': 1
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
try:
|
|
73
|
+
check_severity('ALL', data_with_no_risks)
|
|
74
|
+
except SeverityException:
|
|
75
|
+
pytest.fail("SeverityException was raised unexpectedly!")
|
pyntcli-0.1.92.dist-info/RECORD
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
pyntcli/__init__.py,sha256=jibR5k16CvIIIRqIusW7mrU6CMM0wet7LwtTuFLXbmU,23
|
|
2
|
-
pyntcli/main.py,sha256=ucC9d4wlhUpvA92OXRIZ55iMdFXEDiAOVvP_wtRu7zs,6432
|
|
3
|
-
pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pyntcli/analytics/send.py,sha256=9TRAEoPbv4rWOZfcNaGanrRJAFvNs39P-uKSl49GcQE,3679
|
|
5
|
-
pyntcli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
pyntcli/auth/login.py,sha256=TljsRXbEkNI1YUrKm5mlTw4YiecYScYUsit8Z8vstss,5228
|
|
7
|
-
pyntcli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pyntcli/commands/burp.py,sha256=NfvMPlw8yISaDzvSuF96MXGVTPKRS7yR84TNDqFiblU,12274
|
|
9
|
-
pyntcli/commands/command.py,sha256=mYeNer6WeswYLPSZhvNC04S5FOdMqH-Jk8a4iPRJtpc,10973
|
|
10
|
-
pyntcli/commands/har.py,sha256=IZGd900hz0fFM3iZTG3NNXB2NiuAJGeuv-I49eXrf-o,4158
|
|
11
|
-
pyntcli/commands/id_command.py,sha256=UBEgMIpm4vauTCsKyixltiGUolNg_OfHEJvJ_i5BpJY,943
|
|
12
|
-
pyntcli/commands/listen.py,sha256=0wY9itDQyxSeHQA4GCGF67Xkb4ym48uP17WCPJMcLBE,8910
|
|
13
|
-
pyntcli/commands/newman.py,sha256=ocg85L7HopXRYkhLc75QUtHH9ey48Z6Yxy1stgVW1aE,5283
|
|
14
|
-
pyntcli/commands/postman.py,sha256=T1qdFHXX_kTW9p8PPbY9P7ynK4UcGoG3FfEMrRetwco,5163
|
|
15
|
-
pyntcli/commands/pynt_cmd.py,sha256=X39hiDq6blGh2sGpwVeUXOgnDlhHruGkOoR5aUBzw_k,2982
|
|
16
|
-
pyntcli/commands/root.py,sha256=-2aAiOKc_yXAlymxGove6su1mcvYsk4kCNaEawDKwy8,3677
|
|
17
|
-
pyntcli/commands/static_file_extensions.py,sha256=PZJb02BI-64tbU-j3rdCNsXzTh7gkIDGxGKbKNw3h5k,1995
|
|
18
|
-
pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
|
|
19
|
-
pyntcli/commands/util.py,sha256=4p4rzNqL8nUJJfj1uy_JOHsFBEvVT6-k-V9GbAqHVyM,3218
|
|
20
|
-
pyntcli/log/__init__.py,sha256=cOGwOYzMoshEbZiiasBGkj6wF0SBu3Jdpl-AuakDesw,19
|
|
21
|
-
pyntcli/log/log.py,sha256=cWCdWmUaAwePwdhYDcgNMEG9d9RM34sGahxBCYEdv2Y,1069
|
|
22
|
-
pyntcli/pynt_docker/__init__.py,sha256=PQIOVxc7XXtMLfEX7ojgwf_Z3mmTllO3ZvzUZTPOxQY,30
|
|
23
|
-
pyntcli/pynt_docker/container_utils.py,sha256=_Onn7loInzyJAG2-Uk6CGpsuRyelmUFHOvtJj4Uzi9A,175
|
|
24
|
-
pyntcli/pynt_docker/pynt_container.py,sha256=sqWKi3HUIn3AR9HgDYQtofCkaEQiOT3hG1nYQHf8RD8,17500
|
|
25
|
-
pyntcli/store/__init__.py,sha256=xuS9OB21F6B1sUx5XPGxz_6WpG6-KTMbuq50RrZS5OY,29
|
|
26
|
-
pyntcli/store/json_connector.py,sha256=UGs3uORw3iyn0YJ8kzab-veEZToA6d-ByXYuqEleWsA,560
|
|
27
|
-
pyntcli/store/store.py,sha256=4YqmcHRltbbSWewKQoOZH8QdyMFs6i3L8o2bXY_YvSw,1909
|
|
28
|
-
pyntcli/store/store_connector.py,sha256=w4LzcpRZesUZL1f63RmLlWEFRtJ6Y6rcS6PkkGtO4MA,357
|
|
29
|
-
pyntcli/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
pyntcli/transport/pynt_requests.py,sha256=C7OPvcKkRTcxSYuyiWKE59KgA9sRX0d6fm1wnopAmPo,1719
|
|
31
|
-
pyntcli/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
pyntcli/ui/progress.py,sha256=RrnO_jJNunoyupylakmWmHOEPw3lh99OHpKBzL6OBiE,1008
|
|
33
|
-
pyntcli/ui/pynt_errors.py,sha256=00UprD4tFViREv7kuXGQ99PAKGTpXYixxi3Ndeoeiew,689
|
|
34
|
-
pyntcli/ui/report.py,sha256=W-icPSZrGLOubXgam0LpOvHLl_aZg9Zx9qIkL8Ym5PE,1930
|
|
35
|
-
pyntcli/ui/ui_thread.py,sha256=XUBgLpYQjVhrilU-ofw7VSXgTiwneSdTxm61EvC3x4Q,5091
|
|
36
|
-
tests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
|
|
37
|
-
tests/auth/test_login.py,sha256=KFlzWhXBAuwdi7GXf16gCB3ya94LQG2wjcSChE149rQ,3798
|
|
38
|
-
tests/store/test_cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
|
|
39
|
-
pyntcli-0.1.92.dist-info/METADATA,sha256=MS97IJ6CIx7fidgjFBCYP8gKop_deMK3JGRBfXCsj5Y,463
|
|
40
|
-
pyntcli-0.1.92.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
|
|
41
|
-
pyntcli-0.1.92.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
|
|
42
|
-
pyntcli-0.1.92.dist-info/top_level.txt,sha256=u9MDStwVHB7UG8PUcODeWCul_NvzL2EzoLvSlgwLHFs,30
|
|
43
|
-
pyntcli-0.1.92.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|