diff options
-rw-r--r-- | paramiko/client.py | 13 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | sites/www/changelog.rst | 4 | ||||
-rw-r--r-- | tests/test_client.py | 34 |
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, @@ -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 |