summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2014-09-18 18:00:59 -0700
committerAlex Gaynor <alex.gaynor@gmail.com>2014-09-18 18:00:59 -0700
commit848a7209e29d580594d35f299b01db97714caf92 (patch)
tree57425aa20493dccc62bc95e59268fde66eeb368f
parent6ee3f1a074d2b13cca2a5d267f16159f294b98c6 (diff)
parent16a8df33eb4e9723fea244959db2b157be825781 (diff)
Merge branch 'master' into switch-to-cryptography
Conflicts: paramiko/ecdsakey.py tests/test_client.py
-rw-r--r--paramiko/channel.py5
-rw-r--r--paramiko/client.py6
-rw-r--r--paramiko/ecdsakey.py9
-rw-r--r--paramiko/file.py13
-rw-r--r--paramiko/kex_gex.py2
-rw-r--r--paramiko/proxy.py5
-rw-r--r--paramiko/server.py15
-rw-r--r--paramiko/sftp_client.py5
-rw-r--r--paramiko/sftp_file.py6
-rw-r--r--paramiko/sftp_handle.py5
-rw-r--r--paramiko/transport.py11
-rw-r--r--paramiko/util.py9
-rw-r--r--sites/docs/api/keys.rst17
-rw-r--r--sites/www/changelog.rst1
-rw-r--r--tasks.py2
-rw-r--r--tests/test_client.py25
-rwxr-xr-xtests/test_sftp.py20
-rw-r--r--tests/test_transport.py18
18 files changed, 133 insertions, 41 deletions
diff --git a/paramiko/channel.py b/paramiko/channel.py
index 78a14795..9de278cb 100644
--- a/paramiko/channel.py
+++ b/paramiko/channel.py
@@ -38,6 +38,7 @@ from paramiko.ssh_exception import SSHException
from paramiko.file import BufferedFile
from paramiko.buffered_pipe import BufferedPipe, PipeTimeout
from paramiko import pipe
+from paramiko.util import ClosingContextManager
def open_only(func):
@@ -60,7 +61,7 @@ def open_only(func):
return _check
-class Channel (object):
+class Channel (ClosingContextManager):
"""
A secure tunnel across an SSH `.Transport`. A Channel is meant to behave
like a socket, and has an API that should be indistinguishable from the
@@ -73,6 +74,8 @@ class Channel (object):
flow-controlled independently.) Similarly, if the server isn't reading
data you send, calls to `send` may block, unless you set a timeout. This
is exactly like a normal network socket, so it shouldn't be too surprising.
+
+ Instances of this class may be used as context managers.
"""
def __init__(self, chanid):
diff --git a/paramiko/client.py b/paramiko/client.py
index 265389de..05686d97 100644
--- a/paramiko/client.py
+++ b/paramiko/client.py
@@ -37,10 +37,10 @@ from paramiko.resource import ResourceManager
from paramiko.rsakey import RSAKey
from paramiko.ssh_exception import SSHException, BadHostKeyException
from paramiko.transport import Transport
-from paramiko.util import retry_on_signal
+from paramiko.util import retry_on_signal, ClosingContextManager
-class SSHClient (object):
+class SSHClient (ClosingContextManager):
"""
A high-level representation of a session with an SSH server. This class
wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most
@@ -55,6 +55,8 @@ class SSHClient (object):
checking. The default mechanism is to try to use local key files or an
SSH agent (if one is running).
+ Instances of this class may be used as context managers.
+
.. versionadded:: 1.6
"""
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
index 9a000682..fefd5eb5 100644
--- a/paramiko/ecdsakey.py
+++ b/paramiko/ecdsakey.py
@@ -17,7 +17,7 @@
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
-L{ECDSAKey}
+ECDSA keys
"""
import binascii
@@ -131,11 +131,8 @@ class ECDSAKey (PKey):
Generate a new private RSA key. This factory function can be used to
generate a new host key or authentication key.
- @param bits: number of bits the generated key should be.
- @type bits: int
- @param progress_func: Unused.
- @return: new private key
- @rtype: L{RSAKey}
+ :param function progress_func: Unused
+ :returns: A new private key (`.RSAKey`) object
"""
signing_key = SigningKey.generate(curve)
key = ECDSAKey(vals=(signing_key, signing_key.get_verifying_key()))
diff --git a/paramiko/file.py b/paramiko/file.py
index 2238f0bf..311e1982 100644
--- a/paramiko/file.py
+++ b/paramiko/file.py
@@ -19,8 +19,10 @@ from paramiko.common import linefeed_byte_value, crlf, cr_byte, linefeed_byte, \
cr_byte_value
from paramiko.py3compat import BytesIO, PY2, u, b, bytes_types
+from paramiko.util import ClosingContextManager
-class BufferedFile (object):
+
+class BufferedFile (ClosingContextManager):
"""
Reusable base class to implement Python-style file buffering around a
simpler stream.
@@ -104,14 +106,13 @@ class BufferedFile (object):
else:
def __next__(self):
"""
- Returns the next line from the input, or raises L{StopIteration} when
+ Returns the next line from the input, or raises `.StopIteration` when
EOF is hit. Unlike python file objects, it's okay to mix calls to
- C{next} and L{readline}.
+ `.next` and `.readline`.
- @raise StopIteration: when the end of the file is reached.
+ :raises StopIteration: when the end of the file is reached.
- @return: a line read from the file.
- @rtype: str
+ :returns: a line (`str`) read from the file.
"""
line = self.readline()
if not line:
diff --git a/paramiko/kex_gex.py b/paramiko/kex_gex.py
index 5ff8a287..cb548f33 100644
--- a/paramiko/kex_gex.py
+++ b/paramiko/kex_gex.py
@@ -19,7 +19,7 @@
"""
Variant on `KexGroup1 <paramiko.kex_group1.KexGroup1>` where the prime "p" and
generator "g" are provided by the server. A bit more work is required on the
-client side, and a B{lot} more on the server side.
+client side, and a **lot** more on the server side.
"""
import os
diff --git a/paramiko/proxy.py b/paramiko/proxy.py
index 8959b244..0664ac6e 100644
--- a/paramiko/proxy.py
+++ b/paramiko/proxy.py
@@ -26,9 +26,10 @@ from select import select
import socket
from paramiko.ssh_exception import ProxyCommandFailure
+from paramiko.util import ClosingContextManager
-class ProxyCommand(object):
+class ProxyCommand(ClosingContextManager):
"""
Wraps a subprocess running ProxyCommand-driven programs.
@@ -36,6 +37,8 @@ class ProxyCommand(object):
`.Transport` and `.Packetizer` classes. Using this class instead of a
regular socket makes it possible to talk with a Popen'd command that will
proxy traffic between the client and a server hosted in another machine.
+
+ Instances of this class may be used as context managers.
"""
def __init__(self, command_line):
"""
diff --git a/paramiko/server.py b/paramiko/server.py
index cf396b15..bf5039a2 100644
--- a/paramiko/server.py
+++ b/paramiko/server.py
@@ -547,21 +547,18 @@ class ServerInterface (object):
def check_channel_env_request(self, channel, name, value):
"""
Check whether a given environment variable can be specified for the
- given channel. This method should return C{True} if the server
+ given channel. This method should return ``True`` if the server
is willing to set the specified environment variable. Note that
some environment variables (e.g., PATH) can be exceedingly
dangerous, so blindly allowing the client to set the environment
is almost certainly not a good idea.
- The default implementation always returns C{False}.
+ The default implementation always returns ``False``.
- @param channel: the L{Channel} the env request arrived on
- @type channel: L{Channel}
- @param name: foo bar baz
- @type name: str
- @param value: flklj
- @type value: str
- @rtype: bool
+ :param channel: the `.Channel` the env request arrived on
+ :param str name: name
+ :param str value: Channel value
+ :returns: A boolean
"""
return False
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 9c30426d..62127cc2 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -39,6 +39,7 @@ from paramiko.sftp import BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READ
from paramiko.sftp_attr import SFTPAttributes
from paramiko.ssh_exception import SSHException
from paramiko.sftp_file import SFTPFile
+from paramiko.util import ClosingContextManager
def _to_unicode(s):
@@ -58,12 +59,14 @@ def _to_unicode(s):
b_slash = b'/'
-class SFTPClient(BaseSFTP):
+class SFTPClient(BaseSFTP, ClosingContextManager):
"""
SFTP client object.
Used to open an SFTP session across an open SSH `.Transport` and perform
remote file operations.
+
+ Instances of this class may be used as context managers.
"""
def __init__(self, sock):
"""
diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py
index 03d67b33..d0a37da3 100644
--- a/paramiko/sftp_file.py
+++ b/paramiko/sftp_file.py
@@ -488,9 +488,3 @@ class SFTPFile (BufferedFile):
x = self._saved_exception
self._saved_exception = None
raise x
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, traceback):
- self.close()
diff --git a/paramiko/sftp_handle.py b/paramiko/sftp_handle.py
index 92dd9cfe..edceb5ad 100644
--- a/paramiko/sftp_handle.py
+++ b/paramiko/sftp_handle.py
@@ -22,9 +22,10 @@ Abstraction of an SFTP file handle (for server mode).
import os
from paramiko.sftp import SFTP_OP_UNSUPPORTED, SFTP_OK
+from paramiko.util import ClosingContextManager
-class SFTPHandle (object):
+class SFTPHandle (ClosingContextManager):
"""
Abstract object representing a handle to an open file (or folder) in an
SFTP server implementation. Each handle has a string representation used
@@ -32,6 +33,8 @@ class SFTPHandle (object):
Server implementations can (and should) subclass SFTPHandle to implement
features of a file handle, like `stat` or `chattr`.
+
+ Instances of this class may be used as context managers.
"""
def __init__(self, flags=0):
"""
diff --git a/paramiko/transport.py b/paramiko/transport.py
index 50c7a80a..62eae90a 100644
--- a/paramiko/transport.py
+++ b/paramiko/transport.py
@@ -64,7 +64,7 @@ from paramiko.server import ServerInterface
from paramiko.sftp_client import SFTPClient
from paramiko.ssh_exception import (SSHException, BadAuthenticationType,
ChannelException, ProxyCommandFailure)
-from paramiko.util import retry_on_signal, clamp_value
+from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value
@@ -79,13 +79,15 @@ import atexit
atexit.register(_join_lingering_threads)
-class Transport (threading.Thread):
+class Transport (threading.Thread, ClosingContextManager):
"""
An SSH Transport attaches to a stream (usually a socket), negotiates an
encrypted session, authenticates, and then creates stream tunnels, called
`channels <.Channel>`, across the session. Multiple channels can be
multiplexed across a single session (and often are, in the case of port
forwardings).
+
+ Instances of this class may be used as context managers.
"""
_ENCRYPT = object()
_DECRYPT = object()
@@ -1107,10 +1109,9 @@ class Transport (threading.Thread):
def get_banner(self):
"""
Return the banner supplied by the server upon connect. If no banner is
- supplied, this method returns C{None}.
+ supplied, this method returns ``None``.
- @return: server supplied banner, or C{None}.
- @rtype: string
+ :returns: server supplied banner (`str`), or ``None``.
"""
if not self.active or (self.auth_handler is None):
return None
diff --git a/paramiko/util.py b/paramiko/util.py
index f966099d..25062d00 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -290,5 +290,14 @@ def constant_time_bytes_eq(a, b):
res |= byte_ord(a[i]) ^ byte_ord(b[i])
return res == 0
+
+class ClosingContextManager(object):
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
+
def clamp_value(minimum, val, maximum):
return max(minimum, min(val, maximum))
diff --git a/sites/docs/api/keys.rst b/sites/docs/api/keys.rst
index af7b58c4..c6412f77 100644
--- a/sites/docs/api/keys.rst
+++ b/sites/docs/api/keys.rst
@@ -1,6 +1,23 @@
+============
Key handling
============
+Parent key class
+================
+
.. automodule:: paramiko.pkey
+
+DSA (DSS)
+=========
+
.. automodule:: paramiko.dsskey
+
+RSA
+===
+
.. automodule:: paramiko.rsakey
+
+ECDSA
+=====
+
+.. automodule:: paramiko.ecdsakey
diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst
index 38a56101..d0bd481c 100644
--- a/sites/www/changelog.rst
+++ b/sites/www/changelog.rst
@@ -2,6 +2,7 @@
Changelog
=========
+* :release:`1.15.0 <2014-09-18>`
* :support:`393` Replace internal use of PyCrypto's ``SHA.new`` with the
stdlib's ``hashlib.sha1``. Thanks to Alex Gaynor.
* :feature:`267` (also :issue:`250`, :issue:`241`, :issue:`228`) Add GSS-API /
diff --git a/tasks.py b/tasks.py
index b236ee42..3503d019 100644
--- a/tasks.py
+++ b/tasks.py
@@ -46,6 +46,8 @@ def release(ctx):
copytree(docs_build, target)
# Publish
publish(ctx, wheel=True)
+ # Remind
+ print("\n\nDon't forget to update RTD's versions page for new minor releases!")
ns = Collection(test, release, docs=docs, www=www)
diff --git a/tests/test_client.py b/tests/test_client.py
index 49f2a64a..12022172 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -20,7 +20,10 @@
Some unit tests for SSHClient.
"""
+from __future__ import with_statement
+
import gc
+
import socket
from tempfile import mkstemp
import threading
@@ -298,6 +301,28 @@ class SSHClientTest (unittest.TestCase):
self.assertTrue(p() is None)
+ def test_client_can_be_used_as_context_manager(self):
+ """
+ verify that an SSHClient can be used a context manager
+ """
+ threading.Thread(target=self._run).start()
+ host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
+ public_host_key = paramiko.RSAKey(data=host_key.asbytes())
+
+ with paramiko.SSHClient() as tc:
+ self.tc = tc
+ self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.assertEquals(0, len(self.tc.get_host_keys()))
+ self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
+
+ self.event.wait(1.0)
+ self.assertTrue(self.event.isSet())
+ self.assertTrue(self.ts.is_active())
+
+ self.assertTrue(self.tc._transport is not None)
+
+ self.assertTrue(self.tc._transport is None)
+
def test_7_banner_timeout(self):
"""
verify that the SSHClient has a configurable banner timeout.
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 1ae9781d..72c7ba03 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -23,12 +23,13 @@ a real actual sftp server is contacted, and a new folder is created there to
do test file operations in (so no existing files will be harmed).
"""
-from binascii import hexlify
import os
+import socket
import sys
-import warnings
import threading
import unittest
+import warnings
+from binascii import hexlify
from tempfile import mkstemp
import paramiko
@@ -195,6 +196,21 @@ class SFTPTest (unittest.TestCase):
pass
sftp = paramiko.SFTP.from_transport(tc)
+ def test_2_sftp_can_be_used_as_context_manager(self):
+ """
+ verify that the sftp session is closed when exiting the context manager
+ """
+ global sftp
+ with sftp:
+ pass
+ try:
+ sftp.open(FOLDER + '/test2', 'w')
+ self.fail('expected exception')
+ except (EOFError, socket.error):
+ pass
+ finally:
+ sftp = paramiko.SFTP.from_transport(tc)
+
def test_3_write(self):
"""
verify that a file can be created and written, and the size is correct.
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 344d64b8..50b1d86b 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -20,6 +20,8 @@
Some unit tests for the ssh2 protocol in Transport.
"""
+from __future__ import with_statement
+
from binascii import hexlify
import select
import socket
@@ -281,6 +283,22 @@ class TransportTest(unittest.TestCase):
self.assertEqual('Hello there.\n', f.readline())
self.assertEqual('This is on stderr.\n', f.readline())
self.assertEqual('', f.readline())
+
+ def test_6a_channel_can_be_used_as_context_manager(self):
+ """
+ verify that exec_command() does something reasonable.
+ """
+ self.setup_test_server()
+
+ with self.tc.open_session() as chan:
+ with self.ts.accept(1.0) as schan:
+ chan.exec_command('yes')
+ schan.send('Hello there.\n')
+ schan.close()
+
+ f = chan.makefile()
+ self.assertEqual('Hello there.\n', f.readline())
+ self.assertEqual('', f.readline())
def test_7_invoke_shell(self):
"""