ssh-handler 1.0.2__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ssh_handler-1.0.2/ssh_handler.egg-info → ssh_handler-1.0.4}/PKG-INFO +1 -1
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/pyproject.toml +1 -1
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/__init__.py +1 -1
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/core.py +36 -3
- {ssh_handler-1.0.2 → ssh_handler-1.0.4/ssh_handler.egg-info}/PKG-INFO +1 -1
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/tests/test_offline.py +11 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/LICENSE +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/README.md +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/setup.cfg +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/__main__.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/cli.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/config.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/credentials.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/exceptions.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/ftp.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/pool.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/pyqt_worker.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/results.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler/winrm_bootstrap.py +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/SOURCES.txt +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/dependency_links.txt +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/entry_points.txt +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/requires.txt +0 -0
- {ssh_handler-1.0.2 → ssh_handler-1.0.4}/ssh_handler.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ssh-handler"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.4"
|
|
8
8
|
description = "Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -118,10 +118,17 @@ class SSHHandler:
|
|
|
118
118
|
log_callback: Optional[Callable[[str], None]] = None,
|
|
119
119
|
logger: Optional[logging.Logger] = None,
|
|
120
120
|
safe: bool = False,
|
|
121
|
+
quiet: bool = False,
|
|
121
122
|
):
|
|
123
|
+
"""
|
|
124
|
+
:param quiet: when True, suppress the INFO narration (connect/command
|
|
125
|
+
lines) and emit only warnings and errors. Command results
|
|
126
|
+
are returned in CommandResult.stdout regardless.
|
|
127
|
+
"""
|
|
122
128
|
self.config = config
|
|
123
129
|
self._safe_default = safe
|
|
124
130
|
self._log_callback = log_callback
|
|
131
|
+
self._quiet = quiet
|
|
125
132
|
self.log = logger or self._build_logger()
|
|
126
133
|
|
|
127
134
|
self._client: Optional[paramiko.SSHClient] = None
|
|
@@ -143,7 +150,9 @@ class SSHHandler:
|
|
|
143
150
|
logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
144
151
|
)
|
|
145
152
|
logger.addHandler(handler)
|
|
146
|
-
|
|
153
|
+
# set the level every time so quiet= is honored even if the per-host
|
|
154
|
+
# logger was created by an earlier handler instance
|
|
155
|
+
logger.setLevel(logging.WARNING if self._quiet else logging.INFO)
|
|
147
156
|
return logger
|
|
148
157
|
|
|
149
158
|
def _secrets(self) -> list:
|
|
@@ -269,7 +278,12 @@ class SSHHandler:
|
|
|
269
278
|
if cfg.passwordless:
|
|
270
279
|
kwargs["allow_agent"] = True
|
|
271
280
|
kwargs["look_for_keys"] = True
|
|
272
|
-
elif empty_password:
|
|
281
|
+
elif empty_password or pw == "":
|
|
282
|
+
# Explicit empty-password authentication. An empty string means
|
|
283
|
+
# "log in with a blank password" (PermitEmptyPasswords hosts) - send
|
|
284
|
+
# it directly and don't waste the first attempt probing keys/agent,
|
|
285
|
+
# which would otherwise fail with "No authentication methods
|
|
286
|
+
# available" before the empty password is ever tried.
|
|
273
287
|
kwargs["password"] = ""
|
|
274
288
|
kwargs["look_for_keys"] = False
|
|
275
289
|
kwargs["allow_agent"] = False
|
|
@@ -308,9 +322,28 @@ class SSHHandler:
|
|
|
308
322
|
last_auth = exc
|
|
309
323
|
client.close()
|
|
310
324
|
self._emit(logging.DEBUG, f"Auth strategy '{label}' rejected.")
|
|
311
|
-
except
|
|
325
|
+
except paramiko.SSHException as exc:
|
|
312
326
|
client.close()
|
|
327
|
+
# "No authentication methods available" means paramiko had no
|
|
328
|
+
# credential the server would accept (no password/key/agent).
|
|
329
|
+
# That's an auth problem, not a transport one - try the next
|
|
330
|
+
# strategy, and if none are left raise a clear, guided error.
|
|
331
|
+
if "no authentication methods" in str(exc).lower():
|
|
332
|
+
last_auth = exc
|
|
333
|
+
self._emit(logging.DEBUG,
|
|
334
|
+
f"Auth strategy '{label}': {exc}")
|
|
335
|
+
continue
|
|
313
336
|
raise exc
|
|
337
|
+
except OSError as exc:
|
|
338
|
+
client.close()
|
|
339
|
+
raise exc
|
|
340
|
+
if last_auth and "no authentication methods" in str(last_auth).lower():
|
|
341
|
+
raise SSHAuthenticationError(
|
|
342
|
+
f"No usable credentials for {cfg.auth_username}@{cfg.host}. "
|
|
343
|
+
f"Provide one of: password=..., key_filename=..., "
|
|
344
|
+
f"passwordless=True (use your SSH key/agent), or password='' for a "
|
|
345
|
+
f"blank-password account (the server must allow PermitEmptyPasswords)."
|
|
346
|
+
)
|
|
314
347
|
raise SSHAuthenticationError(
|
|
315
348
|
f"All authentication strategies failed for "
|
|
316
349
|
f"{cfg.auth_username}@{cfg.host}: {last_auth}"
|
|
@@ -38,6 +38,17 @@ kw2 = h._build_connect_kwargs(empty_password=True, sock=None)
|
|
|
38
38
|
assert kw2["password"] == "" and kw2["allow_agent"] is False
|
|
39
39
|
print("empty-password strategy OK")
|
|
40
40
|
|
|
41
|
+
# explicit password="" must send a blank password on the PRIMARY attempt
|
|
42
|
+
# (regression guard: previously the primary probed keys and failed with
|
|
43
|
+
# "No authentication methods available" before the empty password was tried)
|
|
44
|
+
hp = SSHHandler.__new__(SSHHandler)
|
|
45
|
+
hp.config = SSHConfig(host="t", username="u", password="")
|
|
46
|
+
hp._jump = None; hp.log = logging.getLogger("x2"); hp._log_callback = None
|
|
47
|
+
kw3 = hp._build_connect_kwargs(empty_password=False, sock=None)
|
|
48
|
+
assert kw3["password"] == "" and kw3["allow_agent"] is False \
|
|
49
|
+
and kw3["look_for_keys"] is False
|
|
50
|
+
print("explicit empty-password (primary) OK")
|
|
51
|
+
|
|
41
52
|
# TransferResult math
|
|
42
53
|
tr = TransferResult("a", "b", "push", "sftp", 1048576, 2.0, 1)
|
|
43
54
|
assert abs(tr.speed_bps - 524288) < 1 and tr.human_size == "1.0MB"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|