Merge lp:~jml/launchpad/extract-ssh-server-auth into lp:launchpad
- extract-ssh-server-auth
- Merge into devel
Proposed by
Jonathan Lange
on 2010-04-15
| Status: | Merged |
|---|---|
| Approved by: | Eleanor Berger on 2010-04-15 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | not available |
| Proposed branch: | lp:~jml/launchpad/extract-ssh-server-auth |
| Merge into: | lp:launchpad |
| Prerequisite: | lp:~jml/launchpad/extract-ssh-server-logging |
| Diff against target: |
620 lines (+252/-125) (has conflicts) 9 files modified
daemons/sftp.tac (+3/-2) lib/lp/codehosting/sshserver/auth.py (+17/-49) lib/lp/codehosting/sshserver/daemon.py (+105/-0) lib/lp/codehosting/sshserver/service.py (+19/-57) lib/lp/codehosting/sshserver/tests/test_auth.py (+7/-7) lib/lp/codehosting/sshserver/tests/test_daemon.py (+93/-0) lib/lp/codehosting/sshserver/tests/test_session.py (+4/-4) lib/lp/codehosting/tests/helpers.py (+0/-2) lib/lp/codehosting/tests/test_sftp.py (+4/-4) Text conflict in daemons/sftp.tac Text conflict in lib/lp/codehosting/sshserver/accesslog.py Text conflict in lib/lp/codehosting/sshserver/service.py Text conflict in lib/lp/codehosting/sshserver/tests/test_auth.py |
| To merge this branch: | bzr merge lp:~jml/launchpad/extract-ssh-server-auth |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Eleanor Berger (community) | code | 2010-04-15 | Approve on 2010-04-15 |
|
Review via email:
|
|||
Commit Message
Move codehosting-
Description of the Change
This is yet another part of the massive branch that splits out the ssh server from codehosting.
This branch takes the codehosting-
Most of the changes are fairly simple. The only subtlety is that the LaunchpadAvatar class now has a new child class called CodehostingAvatar.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'daemons/sftp.tac' |
| 2 | --- daemons/sftp.tac 2010-04-16 19:00:51 +0000 |
| 3 | +++ daemons/sftp.tac 2010-04-16 19:00:53 +0000 |
| 4 | @@ -18,9 +18,10 @@ |
| 5 | # Construct an Application that has the codehosting SSH server. |
| 6 | ======= |
| 7 | |
| 8 | -from lp.codehosting.sshserver.service import ( |
| 9 | +from lp.codehosting.sshserver.daemon import ( |
| 10 | ACCESS_LOG_NAME, get_key_path, LOG_NAME, make_portal, OOPS_CONFIG_SECTION, |
| 11 | - PRIVATE_KEY_FILE, PUBLIC_KEY_FILE, SSHService) |
| 12 | + PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) |
| 13 | +from lp.codehosting.sshserver.service import SSHService |
| 14 | |
| 15 | |
| 16 | # Construct an Application that has the codehosting SSH server. |
| 17 | |
| 18 | === modified file 'lib/lp/codehosting/sshserver/auth.py' |
| 19 | --- lib/lp/codehosting/sshserver/auth.py 2010-04-16 19:00:51 +0000 |
| 20 | +++ lib/lp/codehosting/sshserver/auth.py 2010-04-16 19:00:53 +0000 |
| 21 | @@ -13,7 +13,8 @@ |
| 22 | |
| 23 | __metaclass__ = type |
| 24 | __all__ = [ |
| 25 | - 'get_portal', |
| 26 | + 'LaunchpadAvatar', |
| 27 | + 'PublicKeyFromLaunchpadChecker', |
| 28 | 'SSHUserAuthServer', |
| 29 | ] |
| 30 | |
| 31 | @@ -21,48 +22,47 @@ |
| 32 | |
| 33 | from twisted.conch import avatar |
| 34 | from twisted.conch.error import ConchError |
| 35 | -from twisted.conch.interfaces import IConchUser, ISession |
| 36 | -from twisted.conch.ssh import filetransfer, keys, userauth |
| 37 | +from twisted.conch.interfaces import IConchUser |
| 38 | +from twisted.conch.ssh import keys, userauth |
| 39 | from twisted.conch.ssh.common import getNS, NS |
| 40 | from twisted.conch.checkers import SSHPublicKeyDatabase |
| 41 | |
| 42 | from twisted.cred.error import UnauthorizedLogin |
| 43 | from twisted.cred.checkers import ICredentialsChecker |
| 44 | from twisted.cred import credentials |
| 45 | -from twisted.cred.portal import IRealm, Portal |
| 46 | |
| 47 | from twisted.internet import defer |
| 48 | |
| 49 | -from twisted.python import components, failure |
| 50 | +from twisted.python import failure |
| 51 | |
| 52 | from zope.event import notify |
| 53 | from zope.interface import implements |
| 54 | |
| 55 | from lp.codehosting import sftp |
| 56 | from lp.codehosting.sshserver import events |
| 57 | -from lp.codehosting.sshserver.session import ( |
| 58 | - launch_smart_server, PatchedSSHSession) |
| 59 | +from lp.codehosting.sshserver.session import PatchedSSHSession |
| 60 | from lp.services.twistedsupport.xmlrpc import trap_fault |
| 61 | -from canonical.config import config |
| 62 | from canonical.launchpad.xmlrpc import faults |
| 63 | |
| 64 | |
| 65 | class LaunchpadAvatar(avatar.ConchUser): |
| 66 | """An account on the SSH server, corresponding to a Launchpad person. |
| 67 | |
| 68 | - :ivar branchfs_proxy: A Twisted XML-RPC client for the authserver. The |
| 69 | - server must implement `IBranchFileSystem`. |
| 70 | :ivar channelLookup: See `avatar.ConchUser`. |
| 71 | :ivar subsystemLookup: See `avatar.ConchUser`. |
| 72 | :ivar user_id: The Launchpad database ID of the Person for this account. |
| 73 | :ivar username: The Launchpad username for this account. |
| 74 | """ |
| 75 | |
| 76 | - def __init__(self, userDict, branchfs_proxy): |
| 77 | + def __init__(self, user_dict): |
| 78 | + """Construct a `LaunchpadAvatar`. |
| 79 | + |
| 80 | + :param user_dict: The result of a call to |
| 81 | + `IAuthServer.getUserAndSSHKeys`. |
| 82 | + """ |
| 83 | avatar.ConchUser.__init__(self) |
| 84 | - self.branchfs_proxy = branchfs_proxy |
| 85 | - self.user_id = userDict['id'] |
| 86 | - self.username = userDict['name'] |
| 87 | + self.user_id = user_dict['id'] |
| 88 | + self.username = user_dict['name'] |
| 89 | |
| 90 | # Set the only channel as a standard SSH session (with a couple of bug |
| 91 | # fixes). |
| 92 | @@ -74,34 +74,10 @@ |
| 93 | notify(events.UserLoggedOut(self)) |
| 94 | |
| 95 | |
| 96 | -components.registerAdapter(launch_smart_server, LaunchpadAvatar, ISession) |
| 97 | - |
| 98 | -components.registerAdapter( |
| 99 | - sftp.avatar_to_sftp_server, LaunchpadAvatar, filetransfer.ISFTPServer) |
| 100 | - |
| 101 | - |
| 102 | class UserDisplayedUnauthorizedLogin(UnauthorizedLogin): |
| 103 | """UnauthorizedLogin which should be reported to the user.""" |
| 104 | |
| 105 | |
| 106 | -class Realm: |
| 107 | - implements(IRealm) |
| 108 | - |
| 109 | - def __init__(self, authentication_proxy, branchfs_proxy): |
| 110 | - self.authentication_proxy = authentication_proxy |
| 111 | - self.branchfs_proxy = branchfs_proxy |
| 112 | - |
| 113 | - def requestAvatar(self, avatarId, mind, *interfaces): |
| 114 | - # Fetch the user's details from the authserver |
| 115 | - deferred = mind.lookupUserDetails(self.authentication_proxy, avatarId) |
| 116 | - |
| 117 | - # Once all those details are retrieved, we can construct the avatar. |
| 118 | - def gotUserDict(userDict): |
| 119 | - avatar = LaunchpadAvatar(userDict, self.branchfs_proxy) |
| 120 | - return interfaces[0], avatar, avatar.logout |
| 121 | - return deferred.addCallback(gotUserDict) |
| 122 | - |
| 123 | - |
| 124 | class ISSHPrivateKeyWithMind(credentials.ISSHPrivateKey): |
| 125 | """Marker interface for SSH credentials that reference a Mind.""" |
| 126 | |
| 127 | @@ -303,7 +279,7 @@ |
| 128 | raise UserDisplayedUnauthorizedLogin( |
| 129 | "No such Launchpad account: %s" % credentials.username) |
| 130 | |
| 131 | - def _checkForAuthorizedKey(self, userDict, credentials): |
| 132 | + def _checkForAuthorizedKey(self, user_dict, credentials): |
| 133 | """Check the key data in credentials against the keys found in LP.""" |
| 134 | if credentials.algName == 'ssh-dss': |
| 135 | wantKeyType = 'DSA' |
| 136 | @@ -313,12 +289,12 @@ |
| 137 | # unknown key type |
| 138 | return False |
| 139 | |
| 140 | - if len(userDict['keys']) == 0: |
| 141 | + if len(user_dict['keys']) == 0: |
| 142 | raise UserDisplayedUnauthorizedLogin( |
| 143 | "Launchpad user %r doesn't have a registered SSH key" |
| 144 | % credentials.username) |
| 145 | |
| 146 | - for keytype, keytext in userDict['keys']: |
| 147 | + for keytype, keytext in user_dict['keys']: |
| 148 | if keytype != wantKeyType: |
| 149 | continue |
| 150 | try: |
| 151 | @@ -330,11 +306,3 @@ |
| 152 | raise UnauthorizedLogin( |
| 153 | "Your SSH key does not match any key registered for Launchpad " |
| 154 | "user %s" % credentials.username) |
| 155 | - |
| 156 | - |
| 157 | -def get_portal(authentication_proxy, branchfs_proxy): |
| 158 | - """Get a portal for connecting to Launchpad codehosting.""" |
| 159 | - portal = Portal(Realm(authentication_proxy, branchfs_proxy)) |
| 160 | - portal.registerChecker( |
| 161 | - PublicKeyFromLaunchpadChecker(authentication_proxy)) |
| 162 | - return portal |
| 163 | |
| 164 | === added file 'lib/lp/codehosting/sshserver/daemon.py' |
| 165 | --- lib/lp/codehosting/sshserver/daemon.py 1970-01-01 00:00:00 +0000 |
| 166 | +++ lib/lp/codehosting/sshserver/daemon.py 2010-04-16 19:00:53 +0000 |
| 167 | @@ -0,0 +1,105 @@ |
| 168 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
| 169 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
| 170 | + |
| 171 | +"""Glues the codehosting SSH daemon together.""" |
| 172 | + |
| 173 | +__metaclass__ = type |
| 174 | +__all__ = [ |
| 175 | + 'ACCESS_LOG_NAME', |
| 176 | + 'CodehostingAvatar', |
| 177 | + 'get_key_path', |
| 178 | + 'get_portal', |
| 179 | + 'LOG_NAME', |
| 180 | + 'make_portal', |
| 181 | + 'PRIVATE_KEY_FILE', |
| 182 | + 'PUBLIC_KEY_FILE', |
| 183 | + ] |
| 184 | + |
| 185 | +import os |
| 186 | + |
| 187 | +from twisted.conch.interfaces import ISession |
| 188 | +from twisted.conch.ssh import filetransfer |
| 189 | +from twisted.cred.portal import IRealm, Portal |
| 190 | +from twisted.python import components |
| 191 | +from twisted.web.xmlrpc import Proxy |
| 192 | + |
| 193 | +from zope.interface import implements |
| 194 | + |
| 195 | +from canonical.config import config |
| 196 | +from lp.codehosting import sftp |
| 197 | +from lp.codehosting.sshserver.auth import ( |
| 198 | + LaunchpadAvatar, PublicKeyFromLaunchpadChecker) |
| 199 | +from lp.codehosting.sshserver.session import launch_smart_server |
| 200 | + |
| 201 | + |
| 202 | +# The names of the key files of the server itself. The directory itself is |
| 203 | +# given in config.codehosting.host_key_pair_path. |
| 204 | +PRIVATE_KEY_FILE = 'ssh_host_key_rsa' |
| 205 | +PUBLIC_KEY_FILE = 'ssh_host_key_rsa.pub' |
| 206 | + |
| 207 | +OOPS_CONFIG_SECTION = 'codehosting' |
| 208 | +LOG_NAME = 'codehosting' |
| 209 | +ACCESS_LOG_NAME = 'codehosting.access' |
| 210 | + |
| 211 | + |
| 212 | +class CodehostingAvatar(LaunchpadAvatar): |
| 213 | + """An SSH avatar specific to codehosting. |
| 214 | + |
| 215 | + :ivar branchfs_proxy: A Twisted XML-RPC client for the authserver. The |
| 216 | + server must implement `IBranchFileSystem`. |
| 217 | + """ |
| 218 | + |
| 219 | + def __init__(self, user_dict, branchfs_proxy): |
| 220 | + LaunchpadAvatar.__init__(self, user_dict) |
| 221 | + self.branchfs_proxy = branchfs_proxy |
| 222 | + |
| 223 | + |
| 224 | +components.registerAdapter(launch_smart_server, CodehostingAvatar, ISession) |
| 225 | + |
| 226 | +components.registerAdapter( |
| 227 | + sftp.avatar_to_sftp_server, CodehostingAvatar, filetransfer.ISFTPServer) |
| 228 | + |
| 229 | + |
| 230 | +class Realm: |
| 231 | + implements(IRealm) |
| 232 | + |
| 233 | + def __init__(self, authentication_proxy, branchfs_proxy): |
| 234 | + self.authentication_proxy = authentication_proxy |
| 235 | + self.branchfs_proxy = branchfs_proxy |
| 236 | + |
| 237 | + def requestAvatar(self, avatar_id, mind, *interfaces): |
| 238 | + # Fetch the user's details from the authserver |
| 239 | + deferred = mind.lookupUserDetails( |
| 240 | + self.authentication_proxy, avatar_id) |
| 241 | + |
| 242 | + # Once all those details are retrieved, we can construct the avatar. |
| 243 | + def got_user_dict(user_dict): |
| 244 | + avatar = CodehostingAvatar(user_dict, self.branchfs_proxy) |
| 245 | + return interfaces[0], avatar, avatar.logout |
| 246 | + |
| 247 | + return deferred.addCallback(got_user_dict) |
| 248 | + |
| 249 | + |
| 250 | +def get_portal(authentication_proxy, branchfs_proxy): |
| 251 | + """Get a portal for connecting to Launchpad codehosting.""" |
| 252 | + portal = Portal(Realm(authentication_proxy, branchfs_proxy)) |
| 253 | + portal.registerChecker( |
| 254 | + PublicKeyFromLaunchpadChecker(authentication_proxy)) |
| 255 | + return portal |
| 256 | + |
| 257 | + |
| 258 | +def get_key_path(key_filename): |
| 259 | + key_directory = config.codehosting.host_key_pair_path |
| 260 | + return os.path.join(config.root, key_directory, key_filename) |
| 261 | + |
| 262 | + |
| 263 | +def make_portal(): |
| 264 | + """Create and return a `Portal` for the SSH service. |
| 265 | + |
| 266 | + This portal accepts SSH credentials and returns our customized SSH |
| 267 | + avatars (see `lp.codehosting.sshserver.auth.CodehostingAvatar`). |
| 268 | + """ |
| 269 | + authentication_proxy = Proxy( |
| 270 | + config.codehosting.authentication_endpoint) |
| 271 | + branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) |
| 272 | + return get_portal(authentication_proxy, branchfs_proxy) |
| 273 | |
| 274 | === modified file 'lib/lp/codehosting/sshserver/service.py' |
| 275 | --- lib/lp/codehosting/sshserver/service.py 2010-04-16 19:00:51 +0000 |
| 276 | +++ lib/lp/codehosting/sshserver/service.py 2010-04-16 19:00:53 +0000 |
| 277 | @@ -8,12 +8,6 @@ |
| 278 | |
| 279 | __metaclass__ = type |
| 280 | __all__ = [ |
| 281 | - 'ACCESS_LOG_NAME', |
| 282 | - 'get_key_path', |
| 283 | - 'LOG_NAME', |
| 284 | - 'make_portal', |
| 285 | - 'PRIVATE_KEY_FILE', |
| 286 | - 'PUBLIC_KEY_FILE', |
| 287 | 'SSHService', |
| 288 | ] |
| 289 | |
| 290 | @@ -27,13 +21,11 @@ |
| 291 | from twisted.conch.ssh.transport import SSHServerTransport |
| 292 | from twisted.internet import defer |
| 293 | from twisted.protocols.policies import TimeoutFactory |
| 294 | -from twisted.web.xmlrpc import Proxy |
| 295 | |
| 296 | from zope.event import notify |
| 297 | |
| 298 | -from canonical.config import config |
| 299 | from lp.codehosting.sshserver import accesslog, events |
| 300 | -from lp.codehosting.sshserver.auth import get_portal, SSHUserAuthServer |
| 301 | +from lp.codehosting.sshserver.auth import SSHUserAuthServer |
| 302 | from lp.services.twistedsupport import gatherResults |
| 303 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting |
| 304 | |
| 305 | @@ -43,16 +35,6 @@ |
| 306 | PRIVATE_KEY_FILE = 'ssh_host_key_rsa' |
| 307 | PUBLIC_KEY_FILE = 'ssh_host_key_rsa.pub' |
| 308 | |
| 309 | -OOPS_CONFIG_SECTION = 'codehosting' |
| 310 | -LOG_NAME = 'codehosting' |
| 311 | -ACCESS_LOG_NAME = 'codehosting.access' |
| 312 | - |
| 313 | - |
| 314 | -# The names of the key files of the server itself. The directory itself is |
| 315 | -# given in config.codehosting.host_key_pair_path. |
| 316 | -PRIVATE_KEY_FILE = 'ssh_host_key_rsa' |
| 317 | -PUBLIC_KEY_FILE = 'ssh_host_key_rsa.pub' |
| 318 | - |
| 319 | |
| 320 | class KeepAliveSettingSSHServerTransport(SSHServerTransport): |
| 321 | |
| 322 | @@ -61,44 +43,24 @@ |
| 323 | self.transport.setTcpKeepAlive(True) |
| 324 | |
| 325 | |
| 326 | -<<<<<<< TREE |
| 327 | -def get_key_path(key_filename): |
| 328 | - key_directory = config.codehosting.host_key_pair_path |
| 329 | - return os.path.join(config.root, key_directory, key_filename) |
| 330 | - |
| 331 | - |
| 332 | -def make_portal(): |
| 333 | - """Create and return a `Portal` for the SSH service. |
| 334 | - |
| 335 | - This portal accepts SSH credentials and returns our customized SSH |
| 336 | - avatars (see `lp.codehosting.sshserver.auth.LaunchpadAvatar`). |
| 337 | - """ |
| 338 | - authentication_proxy = Proxy( |
| 339 | - config.codehosting.authentication_endpoint) |
| 340 | - branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) |
| 341 | - return get_portal(authentication_proxy, branchfs_proxy) |
| 342 | - |
| 343 | - |
| 344 | - |
| 345 | -======= |
| 346 | -def get_key_path(key_filename): |
| 347 | - key_directory = config.codehosting.host_key_pair_path |
| 348 | - return os.path.join(config.root, key_directory, key_filename) |
| 349 | - |
| 350 | - |
| 351 | -def make_portal(): |
| 352 | - """Create and return a `Portal` for the SSH service. |
| 353 | - |
| 354 | - This portal accepts SSH credentials and returns our customized SSH |
| 355 | - avatars (see `lp.codehosting.sshserver.auth.LaunchpadAvatar`). |
| 356 | - """ |
| 357 | - authentication_proxy = Proxy( |
| 358 | - config.codehosting.authentication_endpoint) |
| 359 | - branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) |
| 360 | - return get_portal(authentication_proxy, branchfs_proxy) |
| 361 | - |
| 362 | - |
| 363 | ->>>>>>> MERGE-SOURCE |
| 364 | +def get_key_path(key_filename): |
| 365 | + key_directory = config.codehosting.host_key_pair_path |
| 366 | + return os.path.join(config.root, key_directory, key_filename) |
| 367 | + |
| 368 | + |
| 369 | +def make_portal(): |
| 370 | + """Create and return a `Portal` for the SSH service. |
| 371 | + |
| 372 | + This portal accepts SSH credentials and returns our customized SSH |
| 373 | + avatars (see `lp.codehosting.sshserver.auth.LaunchpadAvatar`). |
| 374 | + """ |
| 375 | + authentication_proxy = Proxy( |
| 376 | + config.codehosting.authentication_endpoint) |
| 377 | + branchfs_proxy = Proxy(config.codehosting.branchfs_endpoint) |
| 378 | + return get_portal(authentication_proxy, branchfs_proxy) |
| 379 | + |
| 380 | + |
| 381 | + |
| 382 | class Factory(SSHFactory): |
| 383 | """SSH factory that uses Launchpad's custom authentication. |
| 384 | |
| 385 | |
| 386 | === modified file 'lib/lp/codehosting/sshserver/tests/test_auth.py' |
| 387 | --- lib/lp/codehosting/sshserver/tests/test_auth.py 2010-04-13 18:32:15 +0000 |
| 388 | +++ lib/lp/codehosting/sshserver/tests/test_auth.py 2010-04-16 19:00:53 +0000 |
| 389 | @@ -18,14 +18,13 @@ |
| 390 | from twisted.internet import defer |
| 391 | from twisted.python import failure |
| 392 | from twisted.python.util import sibpath |
| 393 | -from twisted.test.proto_helpers import StringTransport |
| 394 | |
| 395 | from twisted.trial.unittest import TestCase as TrialTestCase |
| 396 | |
| 397 | from canonical.config import config |
| 398 | from canonical.launchpad.xmlrpc import faults |
| 399 | from canonical.testing.layers import TwistedLayer |
| 400 | -from lp.codehosting.sshserver import auth, service |
| 401 | +from lp.codehosting.sshserver import auth |
| 402 | from lp.services.twistedsupport import suppress_stderr |
| 403 | |
| 404 | |
| 405 | @@ -38,14 +37,12 @@ |
| 406 | |
| 407 | implements(IRealm) |
| 408 | |
| 409 | - def requestAvatar(self, avatarId, mind, *interfaces): |
| 410 | + def requestAvatar(self, avatar_id, mind, *interfaces): |
| 411 | user_dict = { |
| 412 | - 'id': avatarId, 'name': avatarId, 'teams': [], |
| 413 | + 'id': avatar_id, 'name': avatar_id, 'teams': [], |
| 414 | 'initialBranches': []} |
| 415 | return ( |
| 416 | - interfaces[0], |
| 417 | - auth.LaunchpadAvatar(user_dict, None), |
| 418 | - lambda: None) |
| 419 | + interfaces[0], auth.LaunchpadAvatar(user_dict), lambda: None) |
| 420 | |
| 421 | |
| 422 | class MockSSHTransport(SSHServerTransport): |
| 423 | @@ -501,6 +498,7 @@ |
| 424 | return d |
| 425 | |
| 426 | |
| 427 | +<<<<<<< TREE |
| 428 | class StringTransportWith_setTcpKeepAlive(StringTransport): |
| 429 | def __init__(self, hostAddress=None, peerAddress=None): |
| 430 | StringTransport.__init__(self, hostAddress, peerAddress) |
| 431 | @@ -569,5 +567,7 @@ |
| 432 | |
| 433 | self.assertNotIdentical(mind1.cache, mind2.cache) |
| 434 | |
| 435 | +======= |
| 436 | +>>>>>>> MERGE-SOURCE |
| 437 | def test_suite(): |
| 438 | return unittest.TestLoader().loadTestsFromName(__name__) |
| 439 | |
| 440 | === added file 'lib/lp/codehosting/sshserver/tests/test_daemon.py' |
| 441 | --- lib/lp/codehosting/sshserver/tests/test_daemon.py 1970-01-01 00:00:00 +0000 |
| 442 | +++ lib/lp/codehosting/sshserver/tests/test_daemon.py 2010-04-16 19:00:53 +0000 |
| 443 | @@ -0,0 +1,93 @@ |
| 444 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
| 445 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
| 446 | + |
| 447 | +"""Tests for the codehosting SSH server glue.""" |
| 448 | + |
| 449 | +__metaclass__ = type |
| 450 | + |
| 451 | +import unittest |
| 452 | + |
| 453 | +from twisted.conch.ssh.common import NS |
| 454 | +from twisted.conch.ssh.keys import Key |
| 455 | +from twisted.test.proto_helpers import StringTransport |
| 456 | +from twisted.trial.unittest import TestCase as TrialTestCase |
| 457 | + |
| 458 | +from canonical.testing.layers import TwistedLayer |
| 459 | + |
| 460 | +from lp.codehosting.sshserver.auth import SSHUserAuthServer |
| 461 | +from lp.codehosting.sshserver.daemon import ( |
| 462 | + get_key_path, get_portal, PRIVATE_KEY_FILE, PUBLIC_KEY_FILE) |
| 463 | +from lp.codehosting.sshserver.service import Factory |
| 464 | + |
| 465 | + |
| 466 | +class StringTransportWith_setTcpKeepAlive(StringTransport): |
| 467 | + def __init__(self, hostAddress=None, peerAddress=None): |
| 468 | + StringTransport.__init__(self, hostAddress, peerAddress) |
| 469 | + self._keepAlive = False |
| 470 | + |
| 471 | + def setTcpKeepAlive(self, flag): |
| 472 | + self._keepAlive = flag |
| 473 | + |
| 474 | + |
| 475 | +class TestFactory(TrialTestCase): |
| 476 | + """Tests for our SSH factory.""" |
| 477 | + |
| 478 | + layer = TwistedLayer |
| 479 | + |
| 480 | + def makeFactory(self): |
| 481 | + """Create and start the factory that our SSH server uses.""" |
| 482 | + factory = Factory( |
| 483 | + get_portal(None, None), |
| 484 | + private_key=Key.fromFile( |
| 485 | + get_key_path(PRIVATE_KEY_FILE)), |
| 486 | + public_key=Key.fromFile( |
| 487 | + get_key_path(PUBLIC_KEY_FILE))) |
| 488 | + factory.startFactory() |
| 489 | + return factory |
| 490 | + |
| 491 | + def startConnecting(self, factory): |
| 492 | + """Connect to the `factory`.""" |
| 493 | + server_transport = factory.buildProtocol(None) |
| 494 | + server_transport.makeConnection(StringTransportWith_setTcpKeepAlive()) |
| 495 | + return server_transport |
| 496 | + |
| 497 | + def test_set_keepalive_on_connection(self): |
| 498 | + # The server transport sets TCP keep alives on the underlying |
| 499 | + # transport. |
| 500 | + factory = self.makeFactory() |
| 501 | + server_transport = self.startConnecting(factory) |
| 502 | + self.assertTrue(server_transport.transport._keepAlive) |
| 503 | + |
| 504 | + def beginAuthentication(self, factory): |
| 505 | + """Connect to `factory` and begin authentication on this connection. |
| 506 | + |
| 507 | + :return: The `SSHServerTransport` after the process of authentication |
| 508 | + has begun. |
| 509 | + """ |
| 510 | + server_transport = self.startConnecting(factory) |
| 511 | + server_transport.ssh_SERVICE_REQUEST(NS('ssh-userauth')) |
| 512 | + self.addCleanup(server_transport.service.serviceStopped) |
| 513 | + return server_transport |
| 514 | + |
| 515 | + def test_authentication_uses_our_userauth_service(self): |
| 516 | + # The service of a SSHServerTransport after authentication has started |
| 517 | + # is an instance of our SSHUserAuthServer class. |
| 518 | + factory = self.makeFactory() |
| 519 | + transport = self.beginAuthentication(factory) |
| 520 | + self.assertIsInstance(transport.service, SSHUserAuthServer) |
| 521 | + |
| 522 | + def test_two_connections_two_minds(self): |
| 523 | + # Two attempts to authenticate do not share the user-details cache. |
| 524 | + factory = self.makeFactory() |
| 525 | + |
| 526 | + server_transport1 = self.beginAuthentication(factory) |
| 527 | + server_transport2 = self.beginAuthentication(factory) |
| 528 | + |
| 529 | + mind1 = server_transport1.service.getMind() |
| 530 | + mind2 = server_transport2.service.getMind() |
| 531 | + |
| 532 | + self.assertNotIdentical(mind1.cache, mind2.cache) |
| 533 | + |
| 534 | + |
| 535 | +def test_suite(): |
| 536 | + return unittest.TestLoader().loadTestsFromName(__name__) |
| 537 | |
| 538 | === modified file 'lib/lp/codehosting/sshserver/tests/test_session.py' |
| 539 | --- lib/lp/codehosting/sshserver/tests/test_session.py 2010-03-15 06:42:34 +0000 |
| 540 | +++ lib/lp/codehosting/sshserver/tests/test_session.py 2010-04-16 19:00:53 +0000 |
| 541 | @@ -14,7 +14,7 @@ |
| 542 | |
| 543 | from canonical.config import config |
| 544 | from lp.codehosting import get_bzr_path, get_BZR_PLUGIN_PATH_for_subprocess |
| 545 | -from lp.codehosting.sshserver.auth import LaunchpadAvatar |
| 546 | +from lp.codehosting.sshserver.daemon import CodehostingAvatar |
| 547 | from lp.codehosting.sshserver.session import ( |
| 548 | ExecOnlySession, ForbiddenCommand, RestrictedExecOnlySession) |
| 549 | from lp.codehosting.tests.helpers import AvatarTestCase |
| 550 | @@ -83,7 +83,7 @@ |
| 551 | |
| 552 | def setUp(self): |
| 553 | AvatarTestCase.setUp(self) |
| 554 | - self.avatar = LaunchpadAvatar(self.aliceUserDict, None) |
| 555 | + self.avatar = CodehostingAvatar(self.aliceUserDict, None) |
| 556 | # The logging system will try to get the id of avatar.transport, so |
| 557 | # let's give it something to take the id of. |
| 558 | self.avatar.transport = object() |
| 559 | @@ -239,7 +239,7 @@ |
| 560 | |
| 561 | def setUp(self): |
| 562 | AvatarTestCase.setUp(self) |
| 563 | - self.avatar = LaunchpadAvatar(self.aliceUserDict, None) |
| 564 | + self.avatar = CodehostingAvatar(self.aliceUserDict, None) |
| 565 | self.reactor = MockReactor() |
| 566 | self.session = RestrictedExecOnlySession( |
| 567 | self.avatar, self.reactor, 'foo', 'bar baz %(user_id)s') |
| 568 | @@ -305,7 +305,7 @@ |
| 569 | |
| 570 | def setUp(self): |
| 571 | AvatarTestCase.setUp(self) |
| 572 | - self.avatar = LaunchpadAvatar(self.aliceUserDict, None) |
| 573 | + self.avatar = CodehostingAvatar(self.aliceUserDict, None) |
| 574 | |
| 575 | def test_avatarAdaptsToRestrictedExecOnlySession(self): |
| 576 | # When Conch tries to adapt the SSH server avatar to ISession, it |
| 577 | |
| 578 | === modified file 'lib/lp/codehosting/tests/helpers.py' |
| 579 | --- lib/lp/codehosting/tests/helpers.py 2009-06-25 04:06:00 +0000 |
| 580 | +++ lib/lp/codehosting/tests/helpers.py 2010-04-16 19:00:53 +0000 |
| 581 | @@ -7,9 +7,7 @@ |
| 582 | __all__ = [ |
| 583 | 'AvatarTestCase', |
| 584 | 'adapt_suite', |
| 585 | - 'BranchTestCase', |
| 586 | 'CodeHostingTestProviderAdapter', |
| 587 | - 'CodeHostingRepositoryTestProviderAdapter', |
| 588 | 'create_branch_with_one_revision', |
| 589 | 'deferToThread', |
| 590 | 'LoomTestMixin', |
| 591 | |
| 592 | === modified file 'lib/lp/codehosting/tests/test_sftp.py' |
| 593 | --- lib/lp/codehosting/tests/test_sftp.py 2010-02-01 04:33:22 +0000 |
| 594 | +++ lib/lp/codehosting/tests/test_sftp.py 2010-04-16 19:00:53 +0000 |
| 595 | @@ -25,7 +25,7 @@ |
| 596 | from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper |
| 597 | from lp.codehosting.sftp import ( |
| 598 | FatLocalTransport, TransportSFTPServer, FileIsADirectory) |
| 599 | -from lp.codehosting.sshserver.auth import LaunchpadAvatar |
| 600 | +from lp.codehosting.sshserver.daemon import CodehostingAvatar |
| 601 | from lp.testing.factory import LaunchpadObjectFactory |
| 602 | from canonical.testing.layers import TwistedLayer |
| 603 | |
| 604 | @@ -110,13 +110,13 @@ |
| 605 | self.branchfs_endpoint = XMLRPCWrapper( |
| 606 | frontend.getFilesystemEndpoint()) |
| 607 | |
| 608 | - def makeLaunchpadAvatar(self): |
| 609 | + def makeCodehostingAvatar(self): |
| 610 | user = self.factory.makePerson() |
| 611 | user_dict = dict(id=user.id, name=user.name) |
| 612 | - return LaunchpadAvatar(user_dict, self.branchfs_endpoint) |
| 613 | + return CodehostingAvatar(user_dict, self.branchfs_endpoint) |
| 614 | |
| 615 | def test_canAdaptToSFTPServer(self): |
| 616 | - avatar = self.makeLaunchpadAvatar() |
| 617 | + avatar = self.makeCodehostingAvatar() |
| 618 | # The adapter logs the SFTPStarted event, which gets the id of the |
| 619 | # transport attribute of 'avatar'. Here we set transport to an |
| 620 | # arbitrary object that can have its id taken. |

There were a few places where moved legacy code didn't conform to our stringent coding conventions, and you kindly offered to fix them before landing this branch.