Merge lp:~mwhudson/launchpad/anon-ssh-hack into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Work in progress
Proposed branch: lp:~mwhudson/launchpad/anon-ssh-hack
Merge into: lp:launchpad
Diff against target: 130 lines (+40/-10)
4 files modified
lib/lp/code/xmlrpc/codehosting.py (+2/-0)
lib/lp/codehosting/sshserver/daemon.py (+20/-10)
lib/lp/services/sshserver/auth.py (+6/-0)
lib/lp/services/sshserver/tests/test_auth.py (+12/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/anon-ssh-hack
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+75442@code.launchpad.net

Description of the change

This is just a RFC really. It allows a user called '+anon' to log in to the codehosting ssh server with no credentials and gives them read only access to public branches. I think the code is OK, though maybe it could use a few more comments.

The question, as ever is: is this something we want to do? On the upside, it allow anonymous users access to a smart server transport for public branches, which should be more efficient than accessing http (we could even change bzr to default to using +anon as the username and bzr+ssh when using lp:// branches). On the downside, it will likely create a bit more load on the codehosting server if it became popular.

To post a comment you must log in.
lp:~mwhudson/launchpad/anon-ssh-hack updated
13957. By Michael Hudson-Doyle

merge devel

Unmerged revisions

13957. By Michael Hudson-Doyle

merge devel

13956. By Michael Hudson-Doyle

fix the unclean reactor error

13955. By Michael Hudson-Doyle

oops

13954. By Michael Hudson-Doyle

test for anonymous login (which fails with an UncleanReactorError, grump)

13953. By Michael Hudson-Doyle

allow a user called +anon to log in (as LAUNCHPAD_ANONYMOUS) without providing any credentials

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
2--- lib/lp/code/xmlrpc/codehosting.py 2011-08-11 21:24:28 +0000
3+++ lib/lp/code/xmlrpc/codehosting.py 2011-11-14 02:22:26 +0000
4@@ -107,6 +107,8 @@
5 method will do whatever security proxy hackery is required to provide read
6 privileges to the Launchpad services.
7 """
8+ if login_id == 0:
9+ login_id = LAUNCHPAD_SERVICES
10 if login_id == LAUNCHPAD_SERVICES or login_id == LAUNCHPAD_ANONYMOUS:
11 # Don't pass in an actual user. Instead pass in LAUNCHPAD_SERVICES
12 # and expect `function` to use `removeSecurityProxy` or similar.
13
14=== modified file 'lib/lp/codehosting/sshserver/daemon.py'
15--- lib/lp/codehosting/sshserver/daemon.py 2010-08-20 20:31:18 +0000
16+++ lib/lp/codehosting/sshserver/daemon.py 2011-11-14 02:22:26 +0000
17@@ -19,15 +19,18 @@
18
19 from twisted.conch.interfaces import ISession
20 from twisted.conch.ssh import filetransfer
21+from twisted.cred import checkers
22 from twisted.cred.portal import (
23 IRealm,
24 Portal,
25 )
26+from twisted.internet import defer
27 from twisted.python import components
28 from twisted.web.xmlrpc import Proxy
29 from zope.interface import implements
30
31 from canonical.config import config
32+from lp.code.interfaces.codehosting import LAUNCHPAD_ANONYMOUS
33 from lp.codehosting import sftp
34 from lp.codehosting.sshserver.session import launch_smart_server
35 from lp.services.sshserver.auth import (
36@@ -71,16 +74,22 @@
37 self.codehosting_proxy = codehosting_proxy
38
39 def requestAvatar(self, avatar_id, mind, *interfaces):
40- # Fetch the user's details from the authserver
41- deferred = mind.lookupUserDetails(
42- self.authentication_proxy, avatar_id)
43-
44- # Once all those details are retrieved, we can construct the avatar.
45- def got_user_dict(user_dict):
46- avatar = CodehostingAvatar(user_dict, self.codehosting_proxy)
47- return interfaces[0], avatar, avatar.logout
48-
49- return deferred.addCallback(got_user_dict)
50+ if avatar_id is checkers.ANONYMOUS:
51+ avatar = CodehostingAvatar(
52+ {'id':0, 'name':LAUNCHPAD_ANONYMOUS},
53+ self.codehosting_proxy)
54+ return defer.succeed((interfaces[0], avatar, avatar.logout))
55+ else:
56+ # Fetch the user's details from the authserver
57+ deferred = mind.lookupUserDetails(
58+ self.authentication_proxy, avatar_id)
59+
60+ # Once all those details are retrieved, we can construct the avatar.
61+ def got_user_dict(user_dict):
62+ avatar = CodehostingAvatar(user_dict, self.codehosting_proxy)
63+ return interfaces[0], avatar, avatar.logout
64+
65+ return deferred.addCallback(got_user_dict)
66
67
68 def get_portal(authentication_proxy, codehosting_proxy):
69@@ -88,6 +97,7 @@
70 portal = Portal(Realm(authentication_proxy, codehosting_proxy))
71 portal.registerChecker(
72 PublicKeyFromLaunchpadChecker(authentication_proxy))
73+ portal.registerChecker(checkers.AllowAnonymousAccess())
74 return portal
75
76
77
78=== modified file 'lib/lp/services/sshserver/auth.py'
79--- lib/lp/services/sshserver/auth.py 2010-08-20 20:31:18 +0000
80+++ lib/lp/services/sshserver/auth.py 2011-11-14 02:22:26 +0000
81@@ -187,6 +187,12 @@
82 d.addErrback(self._ebBadAuth)
83 return d
84
85+ def tryAuth(self, kind, user, data):
86+ if user == '+anon':
87+ return self.portal.login(credentials.Anonymous(), None, IConchUser)
88+ else:
89+ return userauth.SSHUserAuthServer.tryAuth(self, kind, user, data)
90+
91 def _cbFinishedAuth(self, result):
92 ret = userauth.SSHUserAuthServer._cbFinishedAuth(self, result)
93 # Tell the avatar about the transport, so we can tie it to the
94
95=== modified file 'lib/lp/services/sshserver/tests/test_auth.py'
96--- lib/lp/services/sshserver/tests/test_auth.py 2010-11-09 00:14:40 +0000
97+++ lib/lp/services/sshserver/tests/test_auth.py 2011-11-14 02:22:26 +0000
98@@ -9,6 +9,7 @@
99 AsynchronousDeferredRunTest,
100 )
101
102+from twisted.cred import checkers
103 from twisted.conch.checkers import SSHPublicKeyDatabase
104 from twisted.conch.error import ConchError
105 from twisted.conch.ssh import userauth
106@@ -123,6 +124,8 @@
107
108 class TestUserAuthServer(TestCase, UserAuthServerMixin):
109
110+ run_tests_with = AsynchronousDeferredRunTest
111+
112 def setUp(self):
113 TestCase.setUp(self)
114 UserAuthServerMixin.setUp(self)
115@@ -174,6 +177,15 @@
116 self.user_auth.tryAuth = tryAuth
117 self.user_auth._ebBadAuth = _ebBadAuth
118
119+ def test_anonymous_login(self):
120+ self.user_auth.serviceStarted()
121+ self.addCleanup(self.user_auth.serviceStopped)
122+ self.portal.registerChecker(checkers.AllowAnonymousAccess())
123+ def checkAnonymousAvatar((interfaces, avatar, logout)):
124+ self.assertEqual(checkers.ANONYMOUS, avatar.user_id)
125+ self.user_auth.tryAuth('none', '+anon', '').addCallback(
126+ checkAnonymousAvatar)
127+
128
129 class MockChecker(SSHPublicKeyDatabase):
130 """A very simple public key checker which rejects all offered credentials.