launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05905
[Merge] lp:~jelmer/launchpad/custom-ssh-commands into lp:launchpad
Jelmer Vernooij has proposed merging lp:~jelmer/launchpad/custom-ssh-commands into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jelmer/launchpad/custom-ssh-commands/+merge/85339
Refactor the code hosting SSH session to make it easier to support running multiple commands.
Rather than passing in a single allowed_command and a matching command template into RestrictedExecOnlySession, it is now possible to pass in a method that takes the command the user specified and either raises a matching template, or raises ForbiddenCommand.
--
https://code.launchpad.net/~jelmer/launchpad/custom-ssh-commands/+merge/85339
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jelmer/launchpad/custom-ssh-commands into lp:launchpad.
=== modified file 'lib/lp/codehosting/sshserver/session.py'
--- lib/lp/codehosting/sshserver/session.py 2011-02-18 16:16:58 +0000
+++ lib/lp/codehosting/sshserver/session.py 2011-12-12 14:17:29 +0000
@@ -22,8 +22,7 @@
interfaces,
process,
)
-from twisted.python import log, failure
-from twisted.internet.main import CONNECTION_LOST, CONNECTION_DONE
+from twisted.python import log
from canonical.config import config
from lp.codehosting import get_bzr_path
@@ -381,29 +380,27 @@
class RestrictedExecOnlySession(ExecOnlySession):
- """Conch session that only allows a single command to be executed."""
+ """Conch session that only allows specific commands to be executed."""
- def __init__(self, avatar, reactor, allowed_command,
- executed_command_template, environment=None):
+ def __init__(self, avatar, reactor, lookup_command_template,
+ environment=None):
"""Construct a RestrictedExecOnlySession.
:param avatar: See `ExecOnlySession`.
:param reactor: See `ExecOnlySession`.
- :param allowed_command: The sole command that can be executed.
- :param executed_command_template: A Python format string for the actual
- command that will be run. '%(user_id)s' will be replaced with the
- 'user_id' attribute of the current avatar.
+ :param lookup_command_template: Lookup the template for a command.
+ A template is a Python format string for the actual command that
+ will be run. '%(user_id)s' will be replaced with the 'user_id'
+ attribute of the current avatar. Should raise
+ ForbiddenCommand if the command is not allowed.
"""
ExecOnlySession.__init__(self, avatar, reactor, environment)
- self.allowed_command = allowed_command
- self.executed_command_template = executed_command_template
+ self.lookup_command_template = lookup_command_template
@classmethod
- def getAvatarAdapter(klass, allowed_command, executed_command_template,
- environment=None):
+ def getAvatarAdapter(klass, lookup_command_template, environment=None):
from twisted.internet import reactor
- return lambda avatar: klass(
- avatar, reactor, allowed_command, executed_command_template,
+ return lambda avatar: klass(avatar, reactor, lookup_command_template,
environment)
def getCommandToRun(self, command):
@@ -411,10 +408,9 @@
:raise ForbiddenCommand: when `command` is not the allowed command.
"""
- if command != self.allowed_command:
- raise ForbiddenCommand("Not allowed to execute %r." % (command,))
+ executed_command_template = self.lookup_command_template(command)
return ExecOnlySession.getCommandToRun(
- self, self.executed_command_template
+ self, executed_command_template
% {'user_id': self.avatar.user_id})
@@ -448,23 +444,34 @@
arguments, env, protocol)
-def launch_smart_server(avatar):
- from twisted.internet import reactor
+def lookup_command_template(command):
+ """Map a command to a command template.
+ :param command: Command requested by the user
+ :return: Command template
+ :raise ForbiddenCommand: Raised when command isn't allowed
+ """
python_command = "%(root)s/bin/py %(bzr)s" % {
'root': config.root,
'bzr': get_bzr_path(),
}
args = " lp-serve --inet %(user_id)s"
- command = python_command + args
- forking_command = "bzr" + args
+ command_template = python_command + args
+
+ if command == 'bzr serve --inet --directory=/ --allow-writes':
+ return command_template
+ # At the moment, only bzr branch serving is allowed.
+ raise ForbiddenCommand("Not allowed to execute %r." % (command,))
+
+
+def launch_smart_server(avatar):
+ from twisted.internet import reactor
environment = dict(os.environ)
# Extract the hostname from the supermirror root config.
hostname = urlparse.urlparse(config.codehosting.supermirror_root)[1]
environment['BZR_EMAIL'] = '%s@%s' % (avatar.username, hostname)
- klass = RestrictedExecOnlySession
# TODO: Use a FeatureFlag to enable this in a more fine-grained approach.
# If the forking daemon has been spawned, then we can use it if the
# feature is set to true for the given user, etc.
@@ -474,9 +481,8 @@
# forking daemon.
if config.codehosting.use_forking_daemon:
klass = ForkingRestrictedExecOnlySession
- return klass(
- avatar,
- reactor,
- 'bzr serve --inet --directory=/ --allow-writes',
- command,
+ else:
+ klass = RestrictedExecOnlySession
+
+ return klass(avatar, reactor, lookup_command_template,
environment=environment)
=== modified file 'lib/lp/codehosting/sshserver/tests/test_session.py'
--- lib/lp/codehosting/sshserver/tests/test_session.py 2010-10-30 23:27:50 +0000
+++ lib/lp/codehosting/sshserver/tests/test_session.py 2011-12-12 14:17:29 +0000
@@ -24,6 +24,7 @@
ForkingRestrictedExecOnlySession,
RestrictedExecOnlySession,
_WaitForExit,
+ lookup_command_template,
)
from lp.codehosting.tests.helpers import AvatarTestCase
from lp.testing import TestCase
@@ -290,8 +291,12 @@
AvatarTestCase.setUp(self)
self.avatar = CodehostingAvatar(self.aliceUserDict, None)
self.reactor = MockReactor()
+ def lookup_template(command):
+ if command == 'foo':
+ return 'bar baz %(user_id)s'
+ raise ForbiddenCommand("Not allowed to execute %r." % command)
self.session = RestrictedExecOnlySession(
- self.avatar, self.reactor, 'foo', 'bar baz %(user_id)s')
+ self.avatar, self.reactor, lookup_template)
def test_makeRestrictedExecOnlySession(self):
# A RestrictedExecOnlySession is constructed with an avatar, a reactor
@@ -302,9 +307,10 @@
% (self.session,))
self.assertEqual(self.avatar, self.session.avatar)
self.assertEqual(self.reactor, self.session.reactor)
- self.assertEqual('foo', self.session.allowed_command)
self.assertEqual('bar baz %(user_id)s',
- self.session.executed_command_template)
+ self.session.lookup_command_template('foo'))
+ self.assertRaises(ForbiddenCommand,
+ self.session.lookup_command_template, 'notfoo')
def test_execCommandRejectsUnauthorizedCommands(self):
# execCommand rejects all commands except for the command specified in
@@ -336,8 +342,12 @@
# RestrictedExecOnlySession can be easily registered as an adapter for
# Conch avatars.
from twisted.internet import reactor
+ def lookup_template(command):
+ if command == 'foo':
+ return 'bar baz'
+ raise ForbiddenCommand(command)
adapter = RestrictedExecOnlySession.getAvatarAdapter(
- allowed_command='foo', executed_command_template='bar baz')
+ lookup_template)
session = adapter(self.avatar)
self.failUnless(
isinstance(session, RestrictedExecOnlySession),
@@ -345,8 +355,10 @@
"Got %r instead." % (session,))
self.assertIs(self.avatar, session.avatar)
self.assertIs(reactor, session.reactor)
- self.assertEqual('foo', session.allowed_command)
- self.assertEqual('bar baz', session.executed_command_template)
+ self.assertEqual('bar baz',
+ session.lookup_command_template('foo'))
+ self.assertRaises(ForbiddenCommand,
+ session.lookup_command_template, 'notfoo')
class TestSessionIntegration(AvatarTestCase):
@@ -411,3 +423,16 @@
['bzr', 'lp-serve',
'--inet', str(self.avatar.user_id)],
list(arguments))
+
+
+class TestLookupCommand(TestCase):
+
+ def test_other(self):
+ self.assertRaises(ForbiddenCommand, lookup_command_template, 'foo')
+
+ def test_bzr(self):
+ self.assertEquals(
+ config.root + '/bin/py ' + get_bzr_path() +
+ ' lp-serve --inet %(user_id)s',
+ lookup_command_template(
+ 'bzr serve --inet --directory=/ --allow-writes'))