summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.rst2
-rw-r--r--paramiko/__init__.py2
-rw-r--r--paramiko/auth_handler.py4
-rw-r--r--paramiko/client.py6
-rw-r--r--paramiko/common.py31
-rw-r--r--paramiko/py3compat.py20
-rw-r--r--paramiko/sftp_client.py4
-rw-r--r--sites/www/changelog.rst4
-rw-r--r--tasks.py33
-rw-r--r--tests/test_auth.py18
10 files changed, 81 insertions, 43 deletions
diff --git a/README.rst b/README.rst
index 6e49bd68..ab383459 100644
--- a/README.rst
+++ b/README.rst
@@ -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/sites/www/changelog.rst b/sites/www/changelog.rst
index dcffb015..3672e532 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -7,6 +7,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/tasks.py b/tasks.py
index 3b34818c..d5ed25b1 100644
--- a/tasks.py
+++ b/tasks.py
@@ -8,8 +8,21 @@ from invocations.packaging.release import ns as release_coll, publish
from invocations.testing import count_errors
+# TODO: this screams out for the invoke missing-feature of "I just wrap task X,
+# assume its signature by default" (even if that is just **kwargs support)
@task
-def test(ctx, verbose=True, coverage=False, include_slow=False, opts=""):
+def test(
+ ctx,
+ verbose=True,
+ color=True,
+ capture='sys',
+ module=None,
+ k=None,
+ x=False,
+ opts="",
+ coverage=False,
+ include_slow=False,
+):
"""
Run unit tests via pytest.
@@ -20,14 +33,28 @@ def test(ctx, verbose=True, coverage=False, include_slow=False, opts=""):
"""
if verbose and '--verbose' not in opts and '-v' not in opts:
opts += " --verbose"
+ # TODO: forget why invocations.pytest added this; is it to force color when
+ # running headless? Probably?
+ if color:
+ opts += " --color=yes"
+ opts += ' --capture={0}'.format(capture)
if '-m' not in opts and not include_slow:
opts += " -m 'not slow'"
+ if k is not None and not ('-k' in opts if opts else False):
+ opts += ' -k {}'.format(k)
+ if x and not ('-x' in opts if opts else False):
+ opts += ' -x'
+ modstr = ""
+ if module is not None:
+ # NOTE: implicit test_ prefix as we're not on pytest-relaxed yet
+ modstr = " tests/test_{}.py".format(module)
+ # Switch runner depending on coverage or no coverage.
+ # TODO: get pytest's coverage plugin working, IIRC it has issues?
runner = "pytest"
if coverage:
# Leverage how pytest can be run as 'python -m pytest', and then how
# coverage can be told to run things in that manner instead of
# expecting a literal .py file.
- # TODO: get pytest's coverage plugin working, IIRC it has issues?
runner = "coverage run --source=paramiko -m pytest"
# Strip SSH_AUTH_SOCK from parent env to avoid pollution by interactive
# users.
@@ -37,7 +64,7 @@ def test(ctx, verbose=True, coverage=False, include_slow=False, opts=""):
env = dict(os.environ)
if 'SSH_AUTH_SOCK' in env:
del env['SSH_AUTH_SOCK']
- cmd = "{} {}".format(runner, opts)
+ cmd = "{} {} {}".format(runner, opts, modstr)
# NOTE: we have a pytest.ini and tend to use that over PYTEST_ADDOPTS.
ctx.run(cmd, pty=True, env=env, replace_env=True)
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).