Merge lp:~jml/launchpad/codehosting-to-services into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jml/launchpad/codehosting-to-services
Merge into: lp:launchpad
Prerequisite: lp:~jml/launchpad/extract-ssh-server-auth
Diff against target: 594 lines (+191/-134) (has conflicts)
18 files modified
daemons/sftp.tac (+1/-1)
lib/lp/codehosting/sftp.py (+1/-23)
lib/lp/codehosting/sshserver/daemon.py (+3/-3)
lib/lp/codehosting/sshserver/session.py (+19/-75)
lib/lp/codehosting/sshserver/tests/test_daemon.py (+2/-2)
lib/lp/services/sshserver/__init__.py (+8/-0)
lib/lp/services/sshserver/accesslog.py (+1/-1)
lib/lp/services/sshserver/auth.py (+6/-5)
lib/lp/services/sshserver/events.py (+2/-13)
lib/lp/services/sshserver/service.py (+3/-3)
lib/lp/services/sshserver/session.py (+79/-0)
lib/lp/services/sshserver/sftp.py (+35/-0)
lib/lp/services/sshserver/tests/__init__.py (+8/-0)
lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa (+15/-0)
lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub (+1/-0)
lib/lp/services/sshserver/tests/test_accesslog.py (+4/-4)
lib/lp/services/sshserver/tests/test_auth.py (+2/-3)
lib/lp/services/sshserver/tests/test_events.py (+1/-1)
Text conflict in daemons/sftp.tac
Text conflict in lib/lp/services/sshserver/accesslog.py
Text conflict in lib/lp/services/sshserver/service.py
Text conflict in lib/lp/services/sshserver/tests/test_auth.py
To merge this branch: bzr merge lp:~jml/launchpad/codehosting-to-services
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+23491@code.launchpad.net

Commit message

Create a new generic Launchpad SSH server in lp.services.

Description of the change

As best as I can tell, this branch completes the work done in the mega lp:~jml/launchpad/ssh-key-auth branch. It moves all of the generic code out of lp.codehosting.sshserver and into the new lp.services.sshserver.

Most of the changes are simple module moves. There are some exceptions though.

 * PatchedSSHSession is extracted from lp.codehosting.sshserver.session and put into lp.services. It's the only generic chunk from the original module. It's also pretty much necessary for running a production SSH service.

 * Likewise, FileTransferServer is moved out of lp.codehosting.sftp and into lp.services. It is modified slightly so that the SFTPStarted event is generated from it. Behaviour should be equivalent to current behaviour.

 * BazaarSSHStarted and BazaarSSHClosed events are moved to the lp.codehosting session module. Relevant docstrings are updated.

 * I had to copy some keys over for testing.

 * Some tests and docstrings were tweaked to refer to Launchpad SSH server, rather than codehosting.

I look forward to your review.

jml

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote :

<rockstar> jml, all of the codehosting-to-services branch is moves right? No real changes?
<jml> rockstar, pretty much. except for what I mention in my comment.
<rockstar> jml, okay, so basically just docstrings and test comments got changed (outside of imports)
<jml> rockstar, that sounds right.
<rockstar> jml, okay. Looking now.
<rockstar> jml, your comment says you had to copy keys over. Why couldn't you move them?
<jml> rockstar, they are also used by the codehosting acceptance tests.
<rockstar> jml, hm. Do you really think it's wise to duplicate those keys?
<jml> rockstar, why would it be unwise?
<rockstar> jml, duplication. It's not code, this I know. But duplication in general can become unfun.
<jml> rockstar, you want me to generate a new RSA SSH key pair?
<jml> rockstar, it's pretty easy. there's even a cool website that'll do it for you!
<rockstar> jml, no, we can use the same one, but I'm wondering if we could symlink or something.
<rockstar> jml, that sounds like a very silly website. :)
<jml> rockstar, it is – http://sshkeygen.com/
* rockstar groans
<jml> rockstar, anyway, I can make them symlinks if you want. it doesn't matter much either way to me
<rockstar> jml, actually, it doesn't matter much to me either. The question was more or less one of Kiko's "exploratory" questions. If you're okay with it, I am.
<jml> rockstar, cool.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'daemons/sftp.tac'
--- daemons/sftp.tac 2010-04-16 19:01:12 +0000
+++ daemons/sftp.tac 2010-04-16 19:01:18 +0000
@@ -21,7 +21,7 @@
21from lp.codehosting.sshserver.daemon import (21from lp.codehosting.sshserver.daemon import (
22 ACCESS_LOG_NAME, get_key_path, LOG_NAME, make_portal, OOPS_CONFIG_SECTION,22 ACCESS_LOG_NAME, get_key_path, LOG_NAME, make_portal, OOPS_CONFIG_SECTION,
23 PRIVATE_KEY_FILE, PUBLIC_KEY_FILE)23 PRIVATE_KEY_FILE, PUBLIC_KEY_FILE)
24from lp.codehosting.sshserver.service import SSHService24from lp.services.sshserver.service import SSHService
2525
2626
27# Construct an Application that has the codehosting SSH server.27# Construct an Application that has the codehosting SSH server.
2828
=== modified file 'lib/lp/codehosting/sftp.py'
--- lib/lp/codehosting/sftp.py 2010-04-16 19:01:12 +0000
+++ lib/lp/codehosting/sftp.py 2010-04-16 19:01:18 +0000
@@ -15,7 +15,6 @@
15__metaclass__ = type15__metaclass__ = type
16__all__ = [16__all__ = [
17 'avatar_to_sftp_server',17 'avatar_to_sftp_server',
18 'FileTransferServer',
19 'TransportSFTPServer',18 'TransportSFTPServer',
20 ]19 ]
2120
@@ -34,12 +33,10 @@
34from twisted.internet import defer33from twisted.internet import defer
35from twisted.python import util34from twisted.python import util
3635
37from zope.event import notify
38from zope.interface import implements36from zope.interface import implements
3937
38from canonical.config import config
40from lp.codehosting.vfs import AsyncLaunchpadTransport, LaunchpadServer39from lp.codehosting.vfs import AsyncLaunchpadTransport, LaunchpadServer
41from lp.codehosting.sshserver import events
42from canonical.config import config
43from lp.services.twistedsupport import gatherResults40from lp.services.twistedsupport import gatherResults
4441
4542
@@ -253,28 +250,9 @@
253 avatar.branchfs_proxy, user_id, hosted_transport, mirror_transport)250 avatar.branchfs_proxy, user_id, hosted_transport, mirror_transport)
254 server.start_server()251 server.start_server()
255 transport = AsyncLaunchpadTransport(server, server.get_url())252 transport = AsyncLaunchpadTransport(server, server.get_url())
256 notify(events.SFTPStarted(avatar))
257 return TransportSFTPServer(transport)253 return TransportSFTPServer(transport)
258254
259255
260class FileTransferServer(filetransfer.FileTransferServer):
261
262 def __init__(self, data=None, avatar=None):
263 filetransfer.FileTransferServer.__init__(self, data, avatar)
264 self.avatar = avatar
265
266 def connectionLost(self, reason):
267 # This method gets called twice: once from `SSHChannel.closeReceived`
268 # when the client closes the channel and once from `SSHSession.closed`
269 # when the server closes the session. We change the avatar attribute
270 # to avoid logging the `SFTPClosed` event twice.
271 filetransfer.FileTransferServer.connectionLost(self, reason)
272 if self.avatar is not None:
273 avatar = self.avatar
274 self.avatar = None
275 notify(events.SFTPClosed(avatar))
276
277
278class TransportSFTPServer:256class TransportSFTPServer:
279 """An implementation of `ISFTPServer` that backs onto a Bazaar transport.257 """An implementation of `ISFTPServer` that backs onto a Bazaar transport.
280258
281259
=== modified file 'lib/lp/codehosting/sshserver/daemon.py'
--- lib/lp/codehosting/sshserver/daemon.py 2010-04-16 19:01:12 +0000
+++ lib/lp/codehosting/sshserver/daemon.py 2010-04-16 19:01:18 +0000
@@ -27,9 +27,9 @@
2727
28from canonical.config import config28from canonical.config import config
29from lp.codehosting import sftp29from lp.codehosting import sftp
30from lp.codehosting.sshserver.auth import (30from lp.codehosting.sshserver.session import launch_smart_server
31from lp.services.sshserver.auth import (
31 LaunchpadAvatar, PublicKeyFromLaunchpadChecker)32 LaunchpadAvatar, PublicKeyFromLaunchpadChecker)
32from lp.codehosting.sshserver.session import launch_smart_server
3333
3434
35# The names of the key files of the server itself. The directory itself is35# The names of the key files of the server itself. The directory itself is
@@ -97,7 +97,7 @@
97 """Create and return a `Portal` for the SSH service.97 """Create and return a `Portal` for the SSH service.
9898
99 This portal accepts SSH credentials and returns our customized SSH99 This portal accepts SSH credentials and returns our customized SSH
100 avatars (see `lp.codehosting.sshserver.auth.CodehostingAvatar`).100 avatars (see `CodehostingAvatar`).
101 """101 """
102 authentication_proxy = Proxy(102 authentication_proxy = Proxy(
103 config.codehosting.authentication_endpoint)103 config.codehosting.authentication_endpoint)
104104
=== modified file 'lib/lp/codehosting/sshserver/session.py'
--- lib/lp/codehosting/sshserver/session.py 2010-04-16 19:01:12 +0000
+++ lib/lp/codehosting/sshserver/session.py 2010-04-16 19:01:18 +0000
@@ -6,7 +6,6 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'launch_smart_server',8 'launch_smart_server',
9 'PatchedSSHSession',
10 ]9 ]
1110
12import os11import os
@@ -16,81 +15,23 @@
16from zope.interface import implements15from zope.interface import implements
1716
18from twisted.conch.interfaces import ISession17from twisted.conch.interfaces import ISession
19from twisted.conch.ssh import channel, connection, session18from twisted.conch.ssh import connection
20from twisted.internet.process import ProcessExitedAlready19from twisted.internet.process import ProcessExitedAlready
21from twisted.python import log20from twisted.python import log
2221
23from canonical.config import config22from canonical.config import config
24from lp.codehosting import get_bzr_path23from lp.codehosting import get_bzr_path
25from lp.codehosting.sshserver import events24from lp.services.sshserver.events import AvatarEvent
2625
2726
28class PatchedSSHSession(session.SSHSession, object):27class BazaarSSHStarted(AvatarEvent):
29 """Session adapter that corrects bugs in Conch.28
3029 template = '[%(session_id)s] %(username)s started bzr+ssh session.'
31 This object provides no custom logic for Launchpad, it just addresses some30
32 simple bugs in the base `session.SSHSession` class that are not yet fixed31
33 upstream.32class BazaarSSHClosed(AvatarEvent):
34 """33
3534 template = '[%(session_id)s] %(username)s closed bzr+ssh session.'
36 def closeReceived(self):
37 # Without this, the client hangs when it's finished transferring.
38 # XXX: JonathanLange 2009-01-05: This does not appear to have a
39 # corresponding bug in Twisted. We should test that the above comment
40 # is indeed correct and then file a bug upstream.
41 self.loseConnection()
42
43 def loseConnection(self):
44 # XXX: JonathanLange 2008-03-31: This deliberately replaces the
45 # implementation of session.SSHSession.loseConnection. The default
46 # implementation will try to call loseConnection on the client
47 # transport even if it's None. I don't know *why* it is None, so this
48 # doesn't necessarily address the root cause.
49 # See http://twistedmatrix.com/trac/ticket/2754.
50 transport = getattr(self.client, 'transport', None)
51 if transport is not None:
52 transport.loseConnection()
53 # This is called by session.SSHSession.loseConnection. SSHChannel is
54 # the base class of SSHSession.
55 channel.SSHChannel.loseConnection(self)
56
57 def stopWriting(self):
58 """See `session.SSHSession.stopWriting`.
59
60 When the client can't keep up with us, we ask the child process to
61 stop giving us data.
62 """
63 # XXX: MichaelHudson 2008-06-27: Being cagey about whether
64 # self.client.transport is entirely paranoia inspired by the comment
65 # in `loseConnection` above. It would be good to know if and why it is
66 # necessary. See http://twistedmatrix.com/trac/ticket/2754.
67 transport = getattr(self.client, 'transport', None)
68 if transport is not None:
69 # For SFTP connections, 'transport' is actually a _DummyTransport
70 # instance. Neither _DummyTransport nor the protocol it wraps
71 # (filetransfer.FileTransferServer) support pausing.
72 pauseProducing = getattr(transport, 'pauseProducing', None)
73 if pauseProducing is not None:
74 pauseProducing()
75
76 def startWriting(self):
77 """See `session.SSHSession.startWriting`.
78
79 The client is ready for data again, so ask the child to start
80 producing data again.
81 """
82 # XXX: MichaelHudson 2008-06-27: Being cagey about whether
83 # self.client.transport is entirely paranoia inspired by the comment
84 # in `loseConnection` above. It would be good to know if and why it is
85 # necessary. See http://twistedmatrix.com/trac/ticket/2754.
86 transport = getattr(self.client, 'transport', None)
87 if transport is not None:
88 # For SFTP connections, 'transport' is actually a _DummyTransport
89 # instance. Neither _DummyTransport nor the protocol it wraps
90 # (filetransfer.FileTransferServer) support pausing.
91 resumeProducing = getattr(transport, 'resumeProducing', None)
92 if resumeProducing is not None:
93 resumeProducing()
9435
9536
96class ForbiddenCommand(Exception):37class ForbiddenCommand(Exception):
@@ -116,7 +57,10 @@
116 def closed(self):57 def closed(self):
117 """See ISession."""58 """See ISession."""
118 if self._transport is not None:59 if self._transport is not None:
119 notify(events.BazaarSSHClosed(self.avatar))60 # XXX: JonathanLange 2010-04-15: This is something of an
61 # abstraction violation. Apart from this line and its twin, this
62 # class knows nothing about Bazaar.
63 notify(BazaarSSHClosed(self.avatar))
120 try:64 try:
121 self._transport.signalProcess('HUP')65 self._transport.signalProcess('HUP')
122 except (OSError, ProcessExitedAlready):66 except (OSError, ProcessExitedAlready):
@@ -154,9 +98,9 @@
154 "ERROR: %r already running a command on transport %r"98 "ERROR: %r already running a command on transport %r"
155 % (self, self._transport))99 % (self, self._transport))
156 # XXX: JonathanLange 2008-12-23: This is something of an abstraction100 # XXX: JonathanLange 2008-12-23: This is something of an abstraction
157 # violation. Apart from this line, this class knows nothing about101 # violation. Apart from this line and its twin, this class knows
158 # Bazaar.102 # nothing about Bazaar.
159 notify(events.BazaarSSHStarted(self.avatar))103 notify(BazaarSSHStarted(self.avatar))
160 self._transport = self.reactor.spawnProcess(104 self._transport = self.reactor.spawnProcess(
161 protocol, executable, arguments, env=self.environment)105 protocol, executable, arguments, env=self.environment)
162106
163107
=== modified file 'lib/lp/codehosting/sshserver/tests/test_daemon.py'
--- lib/lp/codehosting/sshserver/tests/test_daemon.py 2010-04-16 19:01:12 +0000
+++ lib/lp/codehosting/sshserver/tests/test_daemon.py 2010-04-16 19:01:18 +0000
@@ -14,10 +14,10 @@
1414
15from canonical.testing.layers import TwistedLayer15from canonical.testing.layers import TwistedLayer
1616
17from lp.codehosting.sshserver.auth import SSHUserAuthServer
18from lp.codehosting.sshserver.daemon import (17from lp.codehosting.sshserver.daemon import (
19 get_key_path, get_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE)18 get_key_path, get_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE)
20from lp.codehosting.sshserver.service import Factory19from lp.services.sshserver.auth import SSHUserAuthServer
20from lp.services.sshserver.service import Factory
2121
2222
23class StringTransportWith_setTcpKeepAlive(StringTransport):23class StringTransportWith_setTcpKeepAlive(StringTransport):
2424
=== added directory 'lib/lp/services/sshserver'
=== added file 'lib/lp/services/sshserver/__init__.py'
--- lib/lp/services/sshserver/__init__.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/__init__.py 2010-04-16 19:01:18 +0000
@@ -0,0 +1,8 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""The Launchpad SSH server."""
5
6__metaclass__ = type
7__all__ = []
8
09
=== renamed file 'lib/lp/codehosting/sshserver/accesslog.py' => 'lib/lp/services/sshserver/accesslog.py'
--- lib/lp/codehosting/sshserver/accesslog.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/accesslog.py 2010-04-16 19:01:18 +0000
@@ -17,7 +17,7 @@
17import zope.component.event17import zope.component.event
1818
19from canonical.launchpad.scripts import WatchedFileHandler19from canonical.launchpad.scripts import WatchedFileHandler
20from lp.codehosting.sshserver.events import ILoggingEvent20from lp.services.sshserver.events import ILoggingEvent
21from lp.services.utils import synchronize21from lp.services.utils import synchronize
2222
2323
2424
=== renamed file 'lib/lp/codehosting/sshserver/auth.py' => 'lib/lp/services/sshserver/auth.py'
--- lib/lp/codehosting/sshserver/auth.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/auth.py 2010-04-16 19:01:18 +0000
@@ -38,11 +38,12 @@
38from zope.event import notify38from zope.event import notify
39from zope.interface import implements39from zope.interface import implements
4040
41from lp.codehosting import sftp41from canonical.launchpad.xmlrpc import faults
42from lp.codehosting.sshserver import events42
43from lp.codehosting.sshserver.session import PatchedSSHSession43from lp.services.sshserver import events
44from lp.services.sshserver.sftp import FileTransferServer
45from lp.services.sshserver.session import PatchedSSHSession
44from lp.services.twistedsupport.xmlrpc import trap_fault46from lp.services.twistedsupport.xmlrpc import trap_fault
45from canonical.launchpad.xmlrpc import faults
4647
4748
48class LaunchpadAvatar(avatar.ConchUser):49class LaunchpadAvatar(avatar.ConchUser):
@@ -68,7 +69,7 @@
68 # fixes).69 # fixes).
69 self.channelLookup = {'session': PatchedSSHSession}70 self.channelLookup = {'session': PatchedSSHSession}
70 # ...and set the only subsystem to be SFTP.71 # ...and set the only subsystem to be SFTP.
71 self.subsystemLookup = {'sftp': sftp.FileTransferServer}72 self.subsystemLookup = {'sftp': FileTransferServer}
7273
73 def logout(self):74 def logout(self):
74 notify(events.UserLoggedOut(self))75 notify(events.UserLoggedOut(self))
7576
=== renamed file 'lib/lp/codehosting/sshserver/events.py' => 'lib/lp/services/sshserver/events.py'
--- lib/lp/codehosting/sshserver/events.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/events.py 2010-04-16 19:01:18 +0000
@@ -6,8 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'AuthenticationFailed',8 'AuthenticationFailed',
9 'BazaarSSHClosed',9 'AvatarEvent',
10 'BazaarSSHStarted',
11 'ILoggingEvent',10 'ILoggingEvent',
12 'LoggingEvent',11 'LoggingEvent',
13 'ServerStarting',12 'ServerStarting',
@@ -28,7 +27,7 @@
28class ILoggingEvent(Interface):27class ILoggingEvent(Interface):
29 """An event is a logging event if it has a message and a severity level.28 """An event is a logging event if it has a message and a severity level.
3029
31 Events that provide this interface will be logged in codehosting access30 Events that provide this interface will be logged in the SSH server access
32 log.31 log.
33 """32 """
3433
@@ -143,13 +142,3 @@
143class SFTPClosed(AvatarEvent):142class SFTPClosed(AvatarEvent):
144143
145 template = '[%(session_id)s] %(username)s closed SFTP session.'144 template = '[%(session_id)s] %(username)s closed SFTP session.'
146
147
148class BazaarSSHStarted(AvatarEvent):
149
150 template = '[%(session_id)s] %(username)s started bzr+ssh session.'
151
152
153class BazaarSSHClosed(AvatarEvent):
154
155 template = '[%(session_id)s] %(username)s closed bzr+ssh session.'
156145
=== renamed file 'lib/lp/codehosting/sshserver/service.py' => 'lib/lp/services/sshserver/service.py'
--- lib/lp/codehosting/sshserver/service.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/service.py 2010-04-16 19:01:18 +0000
@@ -1,7 +1,7 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Twisted `service.Service` class for the codehosting SSH server.4"""Twisted `service.Service` class for the Launchpad SSH server.
55
6An `SSHService` object can be used to launch the SSH server.6An `SSHService` object can be used to launch the SSH server.
7"""7"""
@@ -24,8 +24,8 @@
2424
25from zope.event import notify25from zope.event import notify
2626
27from lp.codehosting.sshserver import accesslog, events27from lp.services.sshserver import accesslog, events
28from lp.codehosting.sshserver.auth import SSHUserAuthServer28from lp.services.sshserver.auth import SSHUserAuthServer
29from lp.services.twistedsupport import gatherResults29from lp.services.twistedsupport import gatherResults
30from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting30from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
3131
3232
=== added file 'lib/lp/services/sshserver/session.py'
--- lib/lp/services/sshserver/session.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/session.py 2010-04-16 19:01:18 +0000
@@ -0,0 +1,79 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Patched SSH session for the Launchpad server."""
5
6__metaclass__ = type
7__all__ = [
8 'PatchedSSHSession',
9 ]
10
11from twisted.conch.ssh import channel, session
12
13
14class PatchedSSHSession(session.SSHSession, object):
15 """Session adapter that corrects bugs in Conch.
16
17 This object provides no custom logic for Launchpad, it just addresses some
18 simple bugs in the base `session.SSHSession` class that are not yet fixed
19 upstream.
20 """
21
22 def closeReceived(self):
23 # Without this, the client hangs when it's finished transferring.
24 # XXX: JonathanLange 2009-01-05: This does not appear to have a
25 # corresponding bug in Twisted. We should test that the above comment
26 # is indeed correct and then file a bug upstream.
27 self.loseConnection()
28
29 def loseConnection(self):
30 # XXX: JonathanLange 2008-03-31: This deliberately replaces the
31 # implementation of session.SSHSession.loseConnection. The default
32 # implementation will try to call loseConnection on the client
33 # transport even if it's None. I don't know *why* it is None, so this
34 # doesn't necessarily address the root cause.
35 # See http://twistedmatrix.com/trac/ticket/2754.
36 transport = getattr(self.client, 'transport', None)
37 if transport is not None:
38 transport.loseConnection()
39 # This is called by session.SSHSession.loseConnection. SSHChannel is
40 # the base class of SSHSession.
41 channel.SSHChannel.loseConnection(self)
42
43 def stopWriting(self):
44 """See `session.SSHSession.stopWriting`.
45
46 When the client can't keep up with us, we ask the child process to
47 stop giving us data.
48 """
49 # XXX: MichaelHudson 2008-06-27: Being cagey about whether
50 # self.client.transport is entirely paranoia inspired by the comment
51 # in `loseConnection` above. It would be good to know if and why it is
52 # necessary. See http://twistedmatrix.com/trac/ticket/2754.
53 transport = getattr(self.client, 'transport', None)
54 if transport is not None:
55 # For SFTP connections, 'transport' is actually a _DummyTransport
56 # instance. Neither _DummyTransport nor the protocol it wraps
57 # (filetransfer.FileTransferServer) support pausing.
58 pauseProducing = getattr(transport, 'pauseProducing', None)
59 if pauseProducing is not None:
60 pauseProducing()
61
62 def startWriting(self):
63 """See `session.SSHSession.startWriting`.
64
65 The client is ready for data again, so ask the child to start
66 producing data again.
67 """
68 # XXX: MichaelHudson 2008-06-27: Being cagey about whether
69 # self.client.transport is entirely paranoia inspired by the comment
70 # in `loseConnection` above. It would be good to know if and why it is
71 # necessary. See http://twistedmatrix.com/trac/ticket/2754.
72 transport = getattr(self.client, 'transport', None)
73 if transport is not None:
74 # For SFTP connections, 'transport' is actually a _DummyTransport
75 # instance. Neither _DummyTransport nor the protocol it wraps
76 # (filetransfer.FileTransferServer) support pausing.
77 resumeProducing = getattr(transport, 'resumeProducing', None)
78 if resumeProducing is not None:
79 resumeProducing()
080
=== added file 'lib/lp/services/sshserver/sftp.py'
--- lib/lp/services/sshserver/sftp.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/sftp.py 2010-04-16 19:01:18 +0000
@@ -0,0 +1,35 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Generic SFTP server functionality."""
5
6__metaclass__ = type
7__all__ = [
8 'FileTransferServer',
9 ]
10
11from twisted.conch.ssh import filetransfer
12
13from zope.event import notify
14
15from lp.services.sshserver import events
16
17
18class FileTransferServer(filetransfer.FileTransferServer):
19 """SFTP protocol implementation that logs key events."""
20
21 def __init__(self, data=None, avatar=None):
22 filetransfer.FileTransferServer.__init__(self, data, avatar)
23 notify(events.SFTPStarted(avatar))
24 self.avatar = avatar
25
26 def connectionLost(self, reason):
27 # This method gets called twice: once from `SSHChannel.closeReceived`
28 # when the client closes the channel and once from `SSHSession.closed`
29 # when the server closes the session. We change the avatar attribute
30 # to avoid logging the `SFTPClosed` event twice.
31 filetransfer.FileTransferServer.connectionLost(self, reason)
32 if self.avatar is not None:
33 avatar = self.avatar
34 self.avatar = None
35 notify(events.SFTPClosed(avatar))
036
=== added directory 'lib/lp/services/sshserver/tests'
=== added file 'lib/lp/services/sshserver/tests/__init__.py'
--- lib/lp/services/sshserver/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/tests/__init__.py 2010-04-16 19:01:18 +0000
@@ -0,0 +1,8 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the Launchpad SSH server."""
5
6__metaclass__ = type
7__all__ = []
8
09
=== added directory 'lib/lp/services/sshserver/tests/keys'
=== added file 'lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa'
--- lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa 2010-04-16 19:01:18 +0000
@@ -0,0 +1,15 @@
1-----BEGIN RSA PRIVATE KEY-----
2MIICXAIBAAKBgQDSSpVRPhCiU9PPuZN7QyJdMOgTVwPyYpZGOHutR/9kxFvOLa39
3nY0Eqo39OTumfZBMEEVqIPadQanO9LcdTnl9/Z4LcBGn09EFQ2y7VUkC6J2dSQtr
4YMY0tV+C5HGZ2oYBWKBl5PZ1RI4+qrJpAMMmINdnF0uEE/x8B1iMWGB3PwIBIwKB
5gQCcN2ebb+8ZgBmwQLazVnFMirsHDXClbc630i/9EOmbUAmvGp6B4sCHH5ytevkc
6l8pHIget7JnxKXbUQMKKzJTCpPwwEyL3ZVDxYXg37WQU74cVf93CjOjChs+hOeS1
7sW5m9JFr9oomL5JWnGXr+TV/kYBCNVW++J1Bckn6kYpH+wJBAPUw0ZunXlBRuugA
8YTSmXUUX+ALu6maDD1t7gAk37waxNQMaH5DMk5R4IQtoxeQgCLqL2yEJUqK1lxOy
9wlp1k8UCQQDbj+Vr4poE9MpYNtPDiDqv2aXe6CJ3p1qQuNE0rSxk+0G9h3ASISRw
10DDLgcapg2xkOvG3pidfAJG9P827XiVszAkEA4CyiYm0jB5suivkIaqa7rOK23hxE
11BfQrTFOoQvFPkRcL5ZQ6Hfzes6EIRPIUA8WEUskC3GBLjXLTRTW5Aj+dCwJBAMi+
12E5XWfjBqx6EcL1OvwKDG/g2hCZH4GEnNjBLneQveZ/29qEsW/L43CfHHAiyrD5hx
13w5OxOklF4h02VrZu9EsCQBEZcTAQOrWnkmp7uBrz1V8nFLAE6zFh/6Wj1Mn8k08j
14pcsLJAhm+qlV7EtV/5rk+v3WcXrKiRIiiC0Ron96Dx0=
15-----END RSA PRIVATE KEY-----
016
=== added file 'lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub'
--- lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub 1970-01-01 00:00:00 +0000
+++ lib/lp/services/sshserver/tests/keys/ssh_host_key_rsa.pub 2010-04-16 19:01:18 +0000
@@ -0,0 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0kqVUT4QolPTz7mTe0MiXTDoE1cD8mKWRjh7rUf/ZMRbzi2t/Z2NBKqN/Tk7pn2QTBBFaiD2nUGpzvS3HU55ff2eC3ARp9PRBUNsu1VJAuidnUkLa2DGNLVfguRxmdqGAVigZeT2dUSOPqqyaQDDJiDXZxdLhBP8fAdYjFhgdz8= andrew@frobozz
02
=== renamed file 'lib/lp/codehosting/sshserver/tests/test_logging.py' => 'lib/lp/services/sshserver/tests/test_accesslog.py'
--- lib/lp/codehosting/sshserver/tests/test_logging.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/tests/test_accesslog.py 2010-04-16 19:01:18 +0000
@@ -18,7 +18,7 @@
18import zope.component.event18import zope.component.event
1919
20from canonical.launchpad.scripts import WatchedFileHandler20from canonical.launchpad.scripts import WatchedFileHandler
21from lp.codehosting.sshserver.accesslog import LoggingManager21from lp.services.sshserver.accesslog import LoggingManager
22from lp.testing import TestCase22from lp.testing import TestCase
2323
2424
@@ -71,9 +71,9 @@
71 self.assertEqual(root_handlers, logging.getLogger('').handlers)71 self.assertEqual(root_handlers, logging.getLogger('').handlers)
72 self.assertEqual(bzr_handlers, logging.getLogger('bzr').handlers)72 self.assertEqual(bzr_handlers, logging.getLogger('bzr').handlers)
7373
74 def test_codehosting_log_doesnt_go_to_stderr(self):74 def test_log_doesnt_go_to_stderr(self):
75 # Once logging setup is called, any messages logged to the75 # Once logging setup is called, any messages logged to the
76 # codehosting logger should *not* be logged to stderr. If they are,76 # SSH server logger should *not* be logged to stderr. If they are,
77 # they will appear on the user's terminal.77 # they will appear on the user's terminal.
78 log = self.makeLogger()78 log = self.makeLogger()
79 self.installLoggingManager(log)79 self.installLoggingManager(log)
@@ -136,7 +136,7 @@
136136
137 def test_access_handlers(self):137 def test_access_handlers(self):
138 # The logging setup installs a rotatable log handler that logs output138 # The logging setup installs a rotatable log handler that logs output
139 # to config.codehosting.access_log.139 # to the SSH server access log.
140 directory = self.makeTemporaryDirectory()140 directory = self.makeTemporaryDirectory()
141 access_log = self.makeLogger()141 access_log = self.makeLogger()
142 access_log_path = os.path.join(directory, 'access.log')142 access_log_path = os.path.join(directory, 'access.log')
143143
=== renamed file 'lib/lp/codehosting/sshserver/tests/test_auth.py' => 'lib/lp/services/sshserver/tests/test_auth.py'
--- lib/lp/codehosting/sshserver/tests/test_auth.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/tests/test_auth.py 2010-04-16 19:01:18 +0000
@@ -21,10 +21,9 @@
2121
22from twisted.trial.unittest import TestCase as TrialTestCase22from twisted.trial.unittest import TestCase as TrialTestCase
2323
24from canonical.config import config
25from canonical.launchpad.xmlrpc import faults24from canonical.launchpad.xmlrpc import faults
26from canonical.testing.layers import TwistedLayer25from canonical.testing.layers import TwistedLayer
27from lp.codehosting.sshserver import auth26from lp.services.sshserver import auth
28from lp.services.twistedsupport import suppress_stderr27from lp.services.twistedsupport import suppress_stderr
2928
3029
@@ -224,7 +223,7 @@
224223
225 def test_bannerNotSentOnSuccess(self):224 def test_bannerNotSentOnSuccess(self):
226 # No banner is printed when the user authenticates successfully.225 # No banner is printed when the user authenticates successfully.
227 self.assertEqual(None, config.codehosting.banner)226 self.user_auth._banner = None
228227
229 d = self.requestSuccessfulAuthentication()228 d = self.requestSuccessfulAuthentication()
230 def check(ignored):229 def check(ignored):
231230
=== renamed file 'lib/lp/codehosting/sshserver/tests/test_events.py' => 'lib/lp/services/sshserver/tests/test_events.py'
--- lib/lp/codehosting/sshserver/tests/test_events.py 2010-04-16 19:01:12 +0000
+++ lib/lp/services/sshserver/tests/test_events.py 2010-04-16 19:01:18 +0000
@@ -13,7 +13,7 @@
13import zope.component.event13import zope.component.event
14from zope.event import notify14from zope.event import notify
1515
16from lp.codehosting.sshserver.events import ILoggingEvent, LoggingEvent16from lp.services.sshserver.events import ILoggingEvent, LoggingEvent
1717
18from lp.testing import TestCase18from lp.testing import TestCase
1919