diff options
-rw-r--r-- | README.rst | 2 | ||||
-rw-r--r-- | paramiko/__init__.py | 2 | ||||
-rw-r--r-- | paramiko/auth_handler.py | 4 | ||||
-rw-r--r-- | paramiko/client.py | 6 | ||||
-rw-r--r-- | paramiko/common.py | 31 | ||||
-rw-r--r-- | paramiko/py3compat.py | 20 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 4 | ||||
-rw-r--r-- | paramiko/ssh_gss.py | 7 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | sites/www/changelog.rst | 16 | ||||
-rw-r--r-- | sites/www/installing-1.x.rst | 2 | ||||
-rw-r--r-- | sites/www/installing.rst | 10 | ||||
-rw-r--r-- | tests/test_auth.py | 18 |
13 files changed, 76 insertions, 47 deletions
@@ -11,7 +11,7 @@ Paramiko :Paramiko: Python SSH module :Copyright: Copyright (c) 2003-2009 Robey Pointer <robeypointer@gmail.com> -:Copyright: Copyright (c) 2013-2017 Jeff Forcier <jeff@bitprophet.org> +:Copyright: Copyright (c) 2013-2018 Jeff Forcier <jeff@bitprophet.org> :License: `LGPL <https://www.gnu.org/copyleft/lesser.html>`_ :Homepage: http://www.paramiko.org/ :API docs: http://docs.paramiko.org diff --git a/paramiko/__init__.py b/paramiko/__init__.py index c4c69a45..4d80bf26 100644 --- a/paramiko/__init__.py +++ b/paramiko/__init__.py @@ -84,6 +84,8 @@ __all__ = [ 'PKey', 'RSAKey', 'DSSKey', + 'ECDSAKey', + 'Ed25519Key', 'Message', 'SSHException', 'AuthenticationException', diff --git a/paramiko/auth_handler.py b/paramiko/auth_handler.py index a1ce5e3b..3b894de7 100644 --- a/paramiko/auth_handler.py +++ b/paramiko/auth_handler.py @@ -39,7 +39,7 @@ from paramiko.common import ( cMSG_USERAUTH_BANNER ) from paramiko.message import Message -from paramiko.py3compat import bytestring +from paramiko.py3compat import b from paramiko.ssh_exception import ( SSHException, AuthenticationException, BadAuthenticationType, PartialAuthentication, @@ -256,7 +256,7 @@ class AuthHandler (object): m.add_string(self.auth_method) if self.auth_method == 'password': m.add_boolean(False) - password = bytestring(self.password) + password = b(self.password) m.add_string(password) elif self.auth_method == 'publickey': m.add_boolean(True) diff --git a/paramiko/client.py b/paramiko/client.py index 6f0cb847..de0a495e 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -463,6 +463,9 @@ class SSHClient (ClosingContextManager): Python :param int timeout: set command's channel timeout. See `.Channel.settimeout` + :param bool get_pty: + Request a pseudo-terminal from the server (default ``False``). + See `.Channel.get_pty` :param dict environment: a dict of shell environment variables, to be merged into the default environment that the remote command executes within. @@ -476,6 +479,9 @@ class SSHClient (ClosingContextManager): 3-tuple :raises: `.SSHException` -- if the server fails to execute the command + + .. versionchanged:: 1.10 + Added the ``get_pty`` kwarg. """ chan = self._transport.open_session(timeout=timeout) if get_pty: diff --git a/paramiko/common.py b/paramiko/common.py index 11c4121d..eab6647e 100644 --- a/paramiko/common.py +++ b/paramiko/common.py @@ -20,7 +20,7 @@ Common constants and global variables. """ import logging -from paramiko.py3compat import byte_chr, PY2, bytes_types, text_type, long +from paramiko.py3compat import byte_chr, PY2, long, b MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, \ MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT = range(1, 7) @@ -162,17 +162,24 @@ else: def asbytes(s): - """Coerce to bytes if possible or return unchanged.""" - if isinstance(s, bytes_types): - return s - if isinstance(s, text_type): - # Accept text and encode as utf-8 for compatibility only. - return s.encode("utf-8") - asbytes = getattr(s, "asbytes", None) - if asbytes is not None: - return asbytes() - # May be an object that implements the buffer api, let callers handle. - return s + """ + Coerce to bytes if possible or return unchanged. + """ + try: + # Attempt to run through our version of b(), which does the Right Thing + # for string/unicode/buffer (Py2) or bytes/str (Py3), and raises + # TypeError if it's not one of those types. + return b(s) + except TypeError: + try: + # If it wasn't a string/byte/buffer type object, try calling an + # asbytes() method, which many of our internal classes implement. + return s.asbytes() + except AttributeError: + # Finally, just do nothing & assume this object is sufficiently + # byte-y or buffer-y that everything will work out (or that callers + # are capable of handling whatever it is.) + return s xffffffff = long(0xffffffff) diff --git a/paramiko/py3compat.py b/paramiko/py3compat.py index cb9de412..67c0f200 100644 --- a/paramiko/py3compat.py +++ b/paramiko/py3compat.py @@ -1,11 +1,12 @@ import sys import base64 -__all__ = ['PY2', 'string_types', 'integer_types', 'text_type', 'bytes_types', - 'bytes', 'long', 'input', 'decodebytes', 'encodebytes', - 'bytestring', 'byte_ord', 'byte_chr', 'byte_mask', 'b', 'u', 'b2s', - 'StringIO', 'BytesIO', 'is_callable', 'MAXSIZE', - 'next', 'builtins'] +__all__ = [ + 'BytesIO', 'MAXSIZE', 'PY2', 'StringIO', 'b', 'b2s', 'builtins', + 'byte_chr', 'byte_mask', 'byte_ord', 'bytes', 'bytes_types', 'decodebytes', + 'encodebytes', 'input', 'integer_types', 'is_callable', 'long', 'next', + 'string_types', 'text_type', 'u', +] PY2 = sys.version_info[0] < 3 @@ -23,12 +24,6 @@ if PY2: import __builtin__ as builtins - def bytestring(s): # NOQA - if isinstance(s, unicode): # NOQA - return s.encode('utf-8') - return s - - byte_ord = ord # NOQA byte_chr = chr # NOQA @@ -111,9 +106,6 @@ else: decodebytes = base64.decodebytes encodebytes = base64.encodebytes - def bytestring(s): - return s - def byte_ord(c): # In case we're handed a string instead of an int. if not isinstance(c, int): diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index b344dff3..31dc234c 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -28,7 +28,7 @@ from paramiko import util from paramiko.channel import Channel from paramiko.message import Message from paramiko.common import INFO, DEBUG, o777 -from paramiko.py3compat import bytestring, b, u, long +from paramiko.py3compat import b, u, long from paramiko.sftp import ( BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, CMD_NAME, CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE, @@ -489,7 +489,7 @@ class SFTPClient(BaseSFTP, ClosingContextManager): """ dest = self._adjust_cwd(dest) self._log(DEBUG, 'symlink({!r}, {!r})'.format(source, dest)) - source = bytestring(source) + source = b(source) self._request(CMD_SYMLINK, source, dest) def chmod(self, path, mode): diff --git a/paramiko/ssh_gss.py b/paramiko/ssh_gss.py index 88dedf7e..aa7cc74d 100644 --- a/paramiko/ssh_gss.py +++ b/paramiko/ssh_gss.py @@ -42,8 +42,6 @@ GSS_AUTH_AVAILABLE = True GSS_EXCEPTIONS = () -from pyasn1.type.univ import ObjectIdentifier -from pyasn1.codec.der import encoder, decoder #: :var str _API: Constraint for the used API @@ -160,6 +158,8 @@ class _SSH_GSSAuth(object): :note: In server mode we just return the OID length and the DER encoded OID. """ + from pyasn1.type.univ import ObjectIdentifier + from pyasn1.codec.der import encoder OIDs = self._make_uint32(1) krb5_OID = encoder.encode(ObjectIdentifier(self._krb5_mech)) OID_len = self._make_uint32(len(krb5_OID)) @@ -174,6 +174,7 @@ class _SSH_GSSAuth(object): :param str desired_mech: The desired GSS-API mechanism of the client :return: ``True`` if the given OID is supported, otherwise C{False} """ + from pyasn1.codec.der import decoder mech, __ = decoder.decode(desired_mech) if mech.__str__() != self._krb5_mech: return False @@ -260,6 +261,7 @@ class _SSH_GSSAPI(_SSH_GSSAuth): :return: A ``String`` if the GSS-API has returned a token or ``None`` if no token was returned """ + from pyasn1.codec.der import decoder self._username = username self._gss_host = target targ_name = gssapi.Name("host@" + self._gss_host, @@ -428,6 +430,7 @@ class _SSH_SSPI(_SSH_GSSAuth): :return: A ``String`` if the SSPI has returned a token or ``None`` if no token was returned """ + from pyasn1.codec.der import decoder self._username = username self._gss_host = target error = 0 @@ -75,6 +75,5 @@ setup( 'bcrypt>=3.1.3', 'cryptography>=1.5', 'pynacl>=1.0.1', - 'pyasn1>=0.1.7', ], ) diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index b6343c87..3dcebe29 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,18 @@ Changelog ========= +* :support:`1191` Update our install docs with (somewhat) recently added + additional dependencies; we previously only required Cryptography, but the + docs never got updated after we incurred ``bcrypt`` and ``pynacl`` + requirements for Ed25519 key support. + + Additionally, ``pyasn1`` was never actually hard-required; it was necessary + during a development branch, and is used by the optional GSSAPI support, but + is not required for regular installation. Thus, it has been removed from our + ``setup.py`` and its imports in the GSSAPI code made optional. + + Credit to ``@stevenwinfield`` for highlighting the outdated install docs. + * :release:`2.4.1 <2018-03-12>` * :release:`2.3.2 <2018-03-12>` * :release:`2.2.3 <2018-03-12>` @@ -14,6 +26,10 @@ Changelog where authentication status was not checked before processing channel-open and other requests typically only sent after authenticating. Big thanks to Matthijs Kooijman for the report. +* :bug:`1168` Add newer key classes for Ed25519 and ECDSA to + ``paramiko.__all__`` so that code introspecting that attribute, or using + ``from paramiko import *`` (such as some IDEs) sees them. Thanks to + ``@patriksevallius`` for the patch. * :bug:`1039` Ed25519 auth key decryption raised an unexpected exception when given a unicode password string (typical in python 3). Report by Theodor van Nahl and fix by Pierce Lopez. diff --git a/sites/www/installing-1.x.rst b/sites/www/installing-1.x.rst index 8ede40d5..7421a6c2 100644 --- a/sites/www/installing-1.x.rst +++ b/sites/www/installing-1.x.rst @@ -118,4 +118,4 @@ First, see the main install doc's notes: :ref:`gssapi` - everything there is required for Paramiko 1.x as well. Additionally, users of Paramiko 1.x, on all platforms, need a final dependency: -`pyasn1 <https://pypi.python.org/pypi/pyasn1>`_ ``0.1.7`` or better. +`pyasn1 <https://pypi.org/project/pyasn1/>`_ ``0.1.7`` or better. diff --git a/sites/www/installing.rst b/sites/www/installing.rst index e6db2dca..3631eb0d 100644 --- a/sites/www/installing.rst +++ b/sites/www/installing.rst @@ -22,8 +22,12 @@ via `pip <http://pip-installer.org>`_:: We currently support **Python 2.7, 3.4+, and PyPy**. Users on Python 2.6 or older (or 3.3 or older) are urged to upgrade. -Paramiko has only one direct hard dependency: the Cryptography library. See -:ref:`cryptography`. +Paramiko has only a few direct dependencies: + +- The big one, with its own sub-dependencies, is Cryptography; see :ref:`its + specific note below <cryptography>` for more details. +- `bcrypt <https://pypi.org/project/bcrypt/>`_, for Ed25519 key support; +- `pynacl <https://pypi.org/project/PyNaCl/>`_, also for Ed25519 key support. If you need GSS-API / SSPI support, see :ref:`the below subsection on it <gssapi>` for details on its optional dependencies. @@ -97,7 +101,7 @@ due to their infrequent utility & non-platform-agnostic requirements): * It hopefully goes without saying but **all platforms** need **a working installation of GSS-API itself**, e.g. Heimdal. -* **Unix** needs `python-gssapi <https://pypi.python.org/pypi/python-gssapi/>`_ +* **Unix** needs `python-gssapi <https://pypi.org/project/python-gssapi/>`_ ``0.6.1`` or better. .. note:: This library appears to only function on Python 2.7 and up. diff --git a/tests/test_auth.py b/tests/test_auth.py index 4eade610..dacdd654 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -133,7 +133,7 @@ class AuthTest (unittest.TestCase): self.assertTrue(self.event.is_set()) self.assertTrue(self.ts.is_active()) - def test_1_bad_auth_type(self): + def test_bad_auth_type(self): """ verify that we get the right exception when an unsupported auth type is requested. @@ -148,7 +148,7 @@ class AuthTest (unittest.TestCase): self.assertEqual(BadAuthenticationType, etype) self.assertEqual(['publickey'], evalue.allowed_types) - def test_2_bad_password(self): + def test_bad_password(self): """ verify that a bad password gets the right exception, and that a retry with the right password works. @@ -164,7 +164,7 @@ class AuthTest (unittest.TestCase): self.tc.auth_password(username='slowdive', password='pygmalion') self.verify_finished() - def test_3_multipart_auth(self): + def test_multipart_auth(self): """ verify that multipart auth works. """ @@ -177,7 +177,7 @@ class AuthTest (unittest.TestCase): self.assertEqual([], remain) self.verify_finished() - def test_4_interactive_auth(self): + def test_interactive_auth(self): """ verify keyboard-interactive auth works. """ @@ -195,7 +195,7 @@ class AuthTest (unittest.TestCase): self.assertEqual([], remain) self.verify_finished() - def test_5_interactive_auth_fallback(self): + def test_interactive_auth_fallback(self): """ verify that a password auth attempt will fallback to "interactive" if password auth isn't supported but interactive is. @@ -206,7 +206,7 @@ class AuthTest (unittest.TestCase): self.assertEqual([], remain) self.verify_finished() - def test_6_auth_utf8(self): + def test_auth_utf8(self): """ verify that utf-8 encoding happens in authentication. """ @@ -216,7 +216,7 @@ class AuthTest (unittest.TestCase): self.assertEqual([], remain) self.verify_finished() - def test_7_auth_non_utf8(self): + def test_auth_non_utf8(self): """ verify that non-utf-8 encoded passwords can be used for broken servers. @@ -227,7 +227,7 @@ class AuthTest (unittest.TestCase): self.assertEqual([], remain) self.verify_finished() - def test_8_auth_gets_disconnected(self): + def test_auth_gets_disconnected(self): """ verify that we catch a server disconnecting during auth, and report it as an auth failure. @@ -241,7 +241,7 @@ class AuthTest (unittest.TestCase): self.assertTrue(issubclass(etype, AuthenticationException)) @slow - def test_9_auth_non_responsive(self): + def test_auth_non_responsive(self): """ verify that authentication times out if server takes to long to respond (or never responds). |