Merge lp:~thumper/launchpad/bzr-transport-branch-id-access into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Merged at revision: 12758
Proposed branch: lp:~thumper/launchpad/bzr-transport-branch-id-access
Merge into: lp:launchpad
Prerequisite: lp:~thumper/launchpad/anon-http-branch-id-access
Diff against target: 213 lines (+120/-9)
3 files modified
lib/lp/code/xmlrpc/codehosting.py (+30/-3)
lib/lp/code/xmlrpc/tests/test_codehosting.py (+58/-0)
lib/lp/codehosting/inmemory.py (+32/-6)
To merge this branch: bzr merge lp:~thumper/launchpad/bzr-transport-branch-id-access
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+56288@code.launchpad.net

Description of the change

This branch provides support for the +branch-id alias over
the smartserver and sftp through our VFS.

All access to branches using the +branch-id alias are read
only.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This is good to land with a couple of additions:

124 + def test_translatePath_branch_id_alias_private_branch(self):

and

135 + def test_translatePath_branch_id_alias_private_branch_no_access(self):

Both need docstrings or explanatory comments so that I don't have to read them to understand what they test.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
--- lib/lp/code/xmlrpc/codehosting.py 2011-02-24 15:30:54 +0000
+++ lib/lp/code/xmlrpc/codehosting.py 2011-04-05 23:58:43 +0000
@@ -59,6 +59,7 @@
59from lp.code.interfaces.branchtarget import IBranchTarget59from lp.code.interfaces.branchtarget import IBranchTarget
60from lp.code.interfaces.codehosting import (60from lp.code.interfaces.codehosting import (
61 BRANCH_ALIAS_PREFIX,61 BRANCH_ALIAS_PREFIX,
62 BRANCH_ID_ALIAS_PREFIX,
62 BRANCH_TRANSPORT,63 BRANCH_TRANSPORT,
63 CONTROL_TRANSPORT,64 CONTROL_TRANSPORT,
64 ICodehostingAPI,65 ICodehostingAPI,
@@ -287,7 +288,8 @@
287288
288 return run_with_login(login_id, branch_changed)289 return run_with_login(login_id, branch_changed)
289290
290 def _serializeBranch(self, requester, branch, trailing_path):291 def _serializeBranch(self, requester, branch, trailing_path,
292 force_readonly=False):
291 if requester == LAUNCHPAD_SERVICES:293 if requester == LAUNCHPAD_SERVICES:
292 branch = removeSecurityProxy(branch)294 branch = removeSecurityProxy(branch)
293 try:295 try:
@@ -296,10 +298,13 @@
296 raise faults.PermissionDenied()298 raise faults.PermissionDenied()
297 if branch.branch_type == BranchType.REMOTE:299 if branch.branch_type == BranchType.REMOTE:
298 return None300 return None
301 if force_readonly:
302 writable = False
303 else:
304 writable = self._canWriteToBranch(requester, branch)
299 return (305 return (
300 BRANCH_TRANSPORT,306 BRANCH_TRANSPORT,
301 {'id': branch_id,307 {'id': branch_id, 'writable': writable},
302 'writable': self._canWriteToBranch(requester, branch)},
303 trailing_path)308 trailing_path)
304309
305 def _serializeControlDirectory(self, requester, product_path,310 def _serializeControlDirectory(self, requester, product_path,
@@ -323,12 +328,34 @@
323 {'default_stack_on': escape('/' + unique_name)},328 {'default_stack_on': escape('/' + unique_name)},
324 trailing_path)329 trailing_path)
325330
331 def _translateBranchIdAlias(self, requester, path):
332 # If the path isn't a branch id alias, nothing more to do.
333 stripped_path = unescape(path.strip('/'))
334 if not stripped_path.startswith(BRANCH_ID_ALIAS_PREFIX + '/'):
335 return None
336 try:
337 parts = stripped_path.split('/', 2)
338 branch_id = int(parts[1])
339 except (ValueError, IndexError):
340 raise faults.PathTranslationError(path)
341 branch = getUtility(IBranchLookup).get(branch_id)
342 if branch is None:
343 raise faults.PathTranslationError(path)
344 try:
345 trailing = parts[2]
346 except IndexError:
347 trailing = ''
348 return self._serializeBranch(requester, branch, trailing, True)
349
326 def translatePath(self, requester_id, path):350 def translatePath(self, requester_id, path):
327 """See `ICodehostingAPI`."""351 """See `ICodehostingAPI`."""
328 @return_fault352 @return_fault
329 def translate_path(requester):353 def translate_path(requester):
330 if not path.startswith('/'):354 if not path.startswith('/'):
331 return faults.InvalidPath(path)355 return faults.InvalidPath(path)
356 branch = self._translateBranchIdAlias(requester, path)
357 if branch is not None:
358 return branch
332 stripped_path = path.strip('/')359 stripped_path = path.strip('/')
333 for first, second in iter_split(stripped_path, '/'):360 for first, second in iter_split(stripped_path, '/'):
334 first = unescape(first)361 first = unescape(first)
335362
=== modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py'
--- lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-10-13 03:56:46 +0000
+++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2011-04-05 23:58:43 +0000
@@ -41,6 +41,7 @@
41from lp.code.interfaces.branchtarget import IBranchTarget41from lp.code.interfaces.branchtarget import IBranchTarget
42from lp.code.interfaces.codehosting import (42from lp.code.interfaces.codehosting import (
43 BRANCH_ALIAS_PREFIX,43 BRANCH_ALIAS_PREFIX,
44 BRANCH_ID_ALIAS_PREFIX,
44 BRANCH_TRANSPORT,45 BRANCH_TRANSPORT,
45 CONTROL_TRANSPORT,46 CONTROL_TRANSPORT,
46 )47 )
@@ -991,6 +992,63 @@
991 requester = self.factory.makePerson()992 requester = self.factory.makePerson()
992 self.assertNotFound(requester, '/%s/.bzr' % BRANCH_ALIAS_PREFIX)993 self.assertNotFound(requester, '/%s/.bzr' % BRANCH_ALIAS_PREFIX)
993994
995 def test_translatePath_branch_id_alias_bzrdir_content(self):
996 # translatePath('/+branch-id/.bzr/.*') *must* return not found, otherwise
997 # bzr will look for it and we don't have a global bzr dir.
998 requester = self.factory.makePerson()
999 self.assertNotFound(
1000 requester, '/%s/.bzr/branch-format' % BRANCH_ID_ALIAS_PREFIX)
1001
1002 def test_translatePath_branch_id_alias_bzrdir(self):
1003 # translatePath('/+branch-id/.bzr') *must* return not found, otherwise
1004 # bzr will look for it and we don't have a global bzr dir.
1005 requester = self.factory.makePerson()
1006 self.assertNotFound(requester, '/%s/.bzr' % BRANCH_ID_ALIAS_PREFIX)
1007
1008 def test_translatePath_branch_id_alias_trailing(self):
1009 # Make sure the trailing path is returned.
1010 requester = self.factory.makePerson()
1011 branch = removeSecurityProxy(self.factory.makeAnyBranch())
1012 path = escape(u'/%s/%s/foo/bar' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
1013 translation = self.codehosting_api.translatePath(requester.id, path)
1014 self.assertEqual(
1015 (BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, 'foo/bar'),
1016 translation)
1017
1018 def test_translatePath_branch_id_alias_owned(self):
1019 # Even if the the requester is the owner, the branch is read only.
1020 requester = self.factory.makePerson()
1021 branch = removeSecurityProxy(
1022 self.factory.makeAnyBranch(
1023 branch_type=BranchType.HOSTED, owner=requester))
1024 path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
1025 translation = self.codehosting_api.translatePath(requester.id, path)
1026 self.assertEqual(
1027 (BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, ''),
1028 translation)
1029
1030 def test_translatePath_branch_id_alias_private_branch(self):
1031 # Private branches are accessible but read-only even if you are the
1032 # owner.
1033 requester = self.factory.makePerson()
1034 branch = removeSecurityProxy(
1035 self.factory.makeAnyBranch(
1036 branch_type=BranchType.HOSTED, private=True, owner=requester))
1037 path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
1038 translation = self.codehosting_api.translatePath(requester.id, path)
1039 self.assertEqual(
1040 (BRANCH_TRANSPORT, {'id': branch.id, 'writable': False}, ''),
1041 translation)
1042
1043 def test_translatePath_branch_id_alias_private_branch_no_access(self):
1044 # Private branches you don't have access to raise permission denied.
1045 requester = self.factory.makePerson()
1046 branch = removeSecurityProxy(
1047 self.factory.makeAnyBranch(
1048 branch_type=BranchType.HOSTED, private=True))
1049 path = escape(u'/%s/%s' % (BRANCH_ID_ALIAS_PREFIX, branch.id))
1050 self.assertPermissionDenied(requester, path)
1051
994 def assertTranslationIsControlDirectory(self, translation,1052 def assertTranslationIsControlDirectory(self, translation,
995 default_stacked_on,1053 default_stacked_on,
996 trailing_path):1054 trailing_path):
9971055
=== modified file 'lib/lp/codehosting/inmemory.py'
--- lib/lp/codehosting/inmemory.py 2011-02-24 15:30:54 +0000
+++ lib/lp/codehosting/inmemory.py 2011-04-05 23:58:43 +0000
@@ -37,6 +37,7 @@
37from lp.code.interfaces.branchtarget import IBranchTarget37from lp.code.interfaces.branchtarget import IBranchTarget
38from lp.code.interfaces.codehosting import (38from lp.code.interfaces.codehosting import (
39 BRANCH_ALIAS_PREFIX,39 BRANCH_ALIAS_PREFIX,
40 BRANCH_ID_ALIAS_PREFIX,
40 BRANCH_TRANSPORT,41 BRANCH_TRANSPORT,
41 CONTROL_TRANSPORT,42 CONTROL_TRANSPORT,
42 LAUNCHPAD_ANONYMOUS,43 LAUNCHPAD_ANONYMOUS,
@@ -806,21 +807,46 @@
806 {'default_stack_on': escape('/' + default_branch.unique_name)},807 {'default_stack_on': escape('/' + default_branch.unique_name)},
807 trailing_path)808 trailing_path)
808809
809 def _serializeBranch(self, requester_id, branch, trailing_path):810 def _serializeBranch(self, requester_id, branch, trailing_path,
811 force_readonly=False):
810 if not self._canRead(requester_id, branch):812 if not self._canRead(requester_id, branch):
811 return faults.PermissionDenied()813 return faults.PermissionDenied()
812 elif branch.branch_type == BranchType.REMOTE:814 elif branch.branch_type == BranchType.REMOTE:
813 return None815 return None
816 if force_readonly:
817 writable = False
814 else:818 else:
815 return (819 writable = self._canWrite(requester_id, branch)
816 BRANCH_TRANSPORT,820 return (
817 {'id': branch.id,821 BRANCH_TRANSPORT,
818 'writable': self._canWrite(requester_id, branch),822 {'id': branch.id, 'writable': writable},
819 }, trailing_path)823 trailing_path)
824
825 def _translateBranchIdAlias(self, requester, path):
826 # If the path isn't a branch id alias, nothing more to do.
827 stripped_path = unescape(path.strip('/'))
828 if not stripped_path.startswith(BRANCH_ID_ALIAS_PREFIX + '/'):
829 return None
830 try:
831 parts = stripped_path.split('/', 2)
832 branch_id = int(parts[1])
833 except (ValueError, IndexError):
834 return faults.PathTranslationError(path)
835 branch = self._branch_set.get(branch_id)
836 if branch is None:
837 return faults.PathTranslationError(path)
838 try:
839 trailing = parts[2]
840 except IndexError:
841 trailing = ''
842 return self._serializeBranch(requester, branch, trailing, True)
820843
821 def translatePath(self, requester_id, path):844 def translatePath(self, requester_id, path):
822 if not path.startswith('/'):845 if not path.startswith('/'):
823 return faults.InvalidPath(path)846 return faults.InvalidPath(path)
847 branch = self._translateBranchIdAlias(requester_id, path)
848 if branch is not None:
849 return branch
824 stripped_path = path.strip('/')850 stripped_path = path.strip('/')
825 for first, second in iter_split(stripped_path, '/'):851 for first, second in iter_split(stripped_path, '/'):
826 first = unescape(first).encode('utf-8')852 first = unescape(first).encode('utf-8')