summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--paramiko/client.py13
-rw-r--r--setup.py3
-rw-r--r--sites/www/changelog.rst4
-rw-r--r--tests/test_client.py34
4 files changed, 52 insertions, 2 deletions
diff --git a/paramiko/client.py b/paramiko/client.py
index 92feaa1f..80cc2ec6 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -237,6 +237,7 @@ class SSHClient(ClosingContextManager):
gss_trust_dns=True,
passphrase=None,
disabled_algorithms=None,
+ transport_factory=None,
):
"""
Connect to an SSH server and authenticate to it. The server's host key
@@ -314,6 +315,12 @@ class SSHClient(ClosingContextManager):
:param dict disabled_algorithms:
an optional dict passed directly to `.Transport` and its keyword
argument of the same name.
+ :param transport_factory:
+ an optional callable which is handed a subset of the constructor
+ arguments (primarily those related to the socket, GSS
+ functionality, and algorithm selection) and generates a
+ `.Transport` instance to be used by this client. Defaults to
+ `.Transport.__init__`.
:raises:
`.BadHostKeyException` -- if the server's host key could not be
@@ -333,6 +340,8 @@ class SSHClient(ClosingContextManager):
Added the ``passphrase`` argument.
.. versionchanged:: 2.6
Added the ``disabled_algorithms`` argument.
+ .. versionchanged:: 2.12
+ Added the ``transport_factory`` argument.
"""
if not sock:
errors = {}
@@ -371,7 +380,9 @@ class SSHClient(ClosingContextManager):
if len(errors) == len(to_try):
raise NoValidConnectionsError(errors)
- t = self._transport = Transport(
+ if transport_factory is None:
+ transport_factory = Transport
+ t = self._transport = transport_factory(
sock,
gss_kex=gss_kex,
gss_deleg_creds=gss_deleg_creds,
diff --git a/setup.py b/setup.py
index c9de0154..a153c076 100644
--- a/setup.py
+++ b/setup.py
@@ -89,7 +89,8 @@ setup(
# use of the extras_require ("paramiko[ed2559]") is now required for those
# TODO 3.0: alternately, given how prevalent ed25519 is now, and how we use
# invoke for (increasing) subproc stuff, consider making the default flavor
- # "full" and adding a "minimal" or similar that is just-crypto?
+ # "full"/"all"? (probably sans gssapi which should remain optional; MAYBE
+ # still sans invoke as well, not everyone uses ProxyCommand or Match exec)
# TODO 3.0: remove six, obviously
install_requires=[
"bcrypt>=3.1.3",
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index 67ea4882..44ae2ee0 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,10 @@
Changelog
=========
+- :feature:`2125` (also re: :issue:`2054`) Add a ``transport_factory`` kwarg to
+ `SSHClient.connect <paramiko.client.SSHClient.connect>` for advanced
+ users to gain more control over early Transport setup and manipulation.
+ Thanks to Noah Pederson for the patch.
- :release:`2.11.1 <2022-11-04>`
- :release:`2.10.6 <2022-11-04>`
- :bug:`1822` (via, and relating to, far too many other issues to mention here)
diff --git a/tests/test_client.py b/tests/test_client.py
index fd54140b..3eaad4fb 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -739,6 +739,40 @@ class SSHClientTest(ClientTest):
call_arg = Transport.call_args[1]["disabled_algorithms"]
assert call_arg == {"keys": ["ssh-dss"]}
+ @patch("paramiko.client.Transport")
+ def test_transport_factory_defaults_to_Transport(self, Transport):
+ sock, kex, creds, algos = Mock(), Mock(), Mock(), Mock()
+ SSHClient().connect(
+ "host",
+ sock=sock,
+ password="no",
+ gss_kex=kex,
+ gss_deleg_creds=creds,
+ disabled_algorithms=algos,
+ )
+ Transport.assert_called_once_with(
+ sock, gss_kex=kex, gss_deleg_creds=creds, disabled_algorithms=algos
+ )
+
+ @patch("paramiko.client.Transport")
+ def test_transport_factory_may_be_specified(self, Transport):
+ factory = Mock()
+ sock, kex, creds, algos = Mock(), Mock(), Mock(), Mock()
+ SSHClient().connect(
+ "host",
+ sock=sock,
+ password="no",
+ gss_kex=kex,
+ gss_deleg_creds=creds,
+ disabled_algorithms=algos,
+ transport_factory=factory,
+ )
+ factory.assert_called_once_with(
+ sock, gss_kex=kex, gss_deleg_creds=creds, disabled_algorithms=algos
+ )
+ # Safety check
+ assert not Transport.called
+
class PasswordPassphraseTests(ClientTest):
# TODO: most of these could reasonably be set up to use mocks/assertions