Merge lp:~wallyworld/launchpad/view-private-branch-artifacts-605130 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 14489
Proposed branch: lp:~wallyworld/launchpad/view-private-branch-artifacts-605130
Merge into: lp:launchpad
Prerequisite: lp:~wallyworld/launchpad/private-owned-entity-traversal-2
Diff against target: 200 lines (+123/-3)
3 files modified
lib/lp/code/browser/branch.py (+9/-1)
lib/lp/code/browser/branchsubscription.py (+14/-2)
lib/lp/code/browser/tests/test_branch.py (+100/-0)
To merge this branch: bzr merge lp:~wallyworld/launchpad/view-private-branch-artifacts-605130
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+85287@code.launchpad.net

This proposal supersedes a proposal from 2011-12-01.

Commit message

[r=sinzui] Grant LimitedView to private team branch subscribers.

Description of the change

Resubmitting to change the prerequisite branch (the prerequisite branch failed qa and needed to be fixed so a new mp has been created for that). Once the new prerequisite branch is approved, this mp will be the one that is landed so both branches are landed together.

== Implementation ==

Use the same technique as for bugs - precache the launchpad.LimitedView permission for the branch owner and subscribers.

The previous version of this branch also solved the traversal issue which prevented branches owned by private teams from being traversed if the user could not see the private team. This work has been moved to an upstream branch.

== Test ==

Add new test case TestBranchViewPrivateArtifacts:
- test_view_branch_with_private_owner
- test_view_private_branch_with_private_owner
- test_anonymous_view_branch_with_private_owner
- test_view_branch_with_private_subscriber
- test_anonymous_view_branch_with_private_subscriber

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/browser/branch.py
  lib/lp/code/browser/branchsubscription.py
  lib/lp/code/browser/tests/test_branch.py

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

This implementation looks good. I think these can be linted and commented, then submitted for a real review. I think this implementation can be applied to the team p3a subscriber case -- that would prove that this implementation is robust.

Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

I think this implementation is good, as are the tests. I suspect a test is missing. As with my review of lp:~wallyworld/launchpad/private-owned-entity-traversal I am not certain the page will render if the private team has an icon. Thus sort of test is easy to add here.

Maybe we want just one test of how tales handles the rendering of a private team with a logo, and and icon when the user has launchpad.LimitedView.

review: Needs Information (code)
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

We spoke about this and agreed we need only one test is needed to verify our tales code handles limitedview of a private team. We expect this to fail because the link formatter wants access to the icon (which is absent from the existing tests of private teams being rendered) and the IPrimaryContext tales adapter to render the logo. We want allow users with limitedview to see name, displayname, icon, and logo.

review: Approve (code)
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

I moved this back to needs review because we need to check the interaction with IPersonPublic.

Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

This branch is okay to land when the pre-req branch has dealt with the IPersonPublic ITeamPublic permission issue.

Revision history for this message
Curtis Hovey (sinzui) wrote :

I am landing this via ec2 land so that It could be in build bot when you wake.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2011-09-28 11:30:52 +0000
3+++ lib/lp/code/browser/branch.py 2011-12-12 05:44:12 +0000
4@@ -96,7 +96,10 @@
5 stepthrough,
6 stepto,
7 )
8-from canonical.launchpad.webapp.authorization import check_permission
9+from canonical.launchpad.webapp.authorization import (
10+ check_permission,
11+ precache_permission_for_objects,
12+ )
13 from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
14 from canonical.launchpad.webapp.menu import structured
15 from lp.app.browser.launchpad import Hierarchy
16@@ -442,6 +445,11 @@
17 def initialize(self):
18 self.branch = self.context
19 self.notices = []
20+ # Cache permission so private team owner can be rendered.
21+ authorised_people = [self.branch.owner]
22+ if self.user is not None:
23+ precache_permission_for_objects(
24+ self.request, "launchpad.LimitedView", authorised_people)
25 # Replace our context with a decorated branch, if it is not already
26 # decorated.
27 if not isinstance(self.context, DecoratedBranch):
28
29=== modified file 'lib/lp/code/browser/branchsubscription.py'
30--- lib/lp/code/browser/branchsubscription.py 2011-09-19 06:57:55 +0000
31+++ lib/lp/code/browser/branchsubscription.py 2011-12-12 05:44:12 +0000
32@@ -19,7 +19,10 @@
33 canonical_url,
34 LaunchpadView,
35 )
36-from canonical.launchpad.webapp.authorization import check_permission
37+from canonical.launchpad.webapp.authorization import (
38+ check_permission,
39+ precache_permission_for_objects,
40+ )
41 from canonical.launchpad.webapp.interfaces import IPrimaryContext
42 from canonical.launchpad.webapp.menu import structured
43 from lp.app.browser.launchpadform import (
44@@ -48,9 +51,18 @@
45
46 def subscriptions(self):
47 """Return a decorated list of branch subscriptions."""
48+
49+ # Cache permissions so private subscribers can be rendered.
50+ if self.user is not None:
51+ subscribers = [
52+ subscription.person
53+ for subscription in self.context.subscriptions]
54+ precache_permission_for_objects(
55+ self.request, "launchpad.LimitedView", subscribers)
56+
57 visible_subscriptions = [
58 subscription for subscription in self.context.subscriptions
59- if check_permission('launchpad.View', subscription.person)]
60+ if check_permission('launchpad.LimitedView', subscription.person)]
61 return sorted(
62 visible_subscriptions,
63 key=lambda subscription: subscription.person.displayname)
64
65=== modified file 'lib/lp/code/browser/tests/test_branch.py'
66--- lib/lp/code/browser/tests/test_branch.py 2011-12-08 21:15:07 +0000
67+++ lib/lp/code/browser/tests/test_branch.py 2011-12-12 05:44:12 +0000
68@@ -5,12 +5,14 @@
69
70 __metaclass__ = type
71
72+from BeautifulSoup import BeautifulSoup
73 from datetime import (
74 datetime,
75 )
76 from textwrap import dedent
77
78 import pytz
79+from zope.publisher.interfaces import NotFound
80 from zope.security.proxy import removeSecurityProxy
81
82 from canonical.config import config
83@@ -26,6 +28,7 @@
84 extract_text,
85 find_tag_by_id,
86 setupBrowser,
87+ setupBrowserForUser
88 )
89 from lp.app.interfaces.headings import IRootContext
90 from lp.bugs.interfaces.bugtask import (
91@@ -50,6 +53,7 @@
92 BranchVisibilityRule,
93 )
94 from lp.code.interfaces.branchtarget import IBranchTarget
95+from lp.registry.interfaces.person import PersonVisibility
96 from lp.testing import (
97 BrowserTestCase,
98 login,
99@@ -541,6 +545,102 @@
100 self.assertEqual(linked_bug_urls[1], links[4]['href'])
101
102
103+class TestBranchViewPrivateArtifacts(BrowserTestCase):
104+ """ Tests that branches with private team artifacts can be viewed.
105+
106+ A Branch may be associated with a private team as follows:
107+ - the owner is a private team
108+ - a subscriber is a private team
109+
110+ A logged in user who is not authorised to see the private team(s) still
111+ needs to be able to view the branch. The private team will be rendered in
112+ the normal way, displaying the team name and Launchpad URL.
113+ """
114+
115+ layer = DatabaseFunctionalLayer
116+
117+ def _getBrowser(self, user=None):
118+ if user is None:
119+ browser = setupBrowser()
120+ logout()
121+ return browser
122+ else:
123+ login_person(user)
124+ return setupBrowserForUser(user=user)
125+
126+ def test_view_branch_with_private_owner(self):
127+ # A branch with a private owner is rendered.
128+ private_owner = self.factory.makeTeam(
129+ displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
130+ branch = self.factory.makeAnyBranch(owner=private_owner)
131+ # Ensure the branch owner is rendered.
132+ url = canonical_url(branch, rootsite='code')
133+ user = self.factory.makePerson()
134+ browser = self._getBrowser(user)
135+ browser.open(url)
136+ soup = BeautifulSoup(browser.contents)
137+ self.assertIsNotNone(soup.find('a', text="PrivateTeam"))
138+
139+ def test_view_private_branch_with_private_owner(self):
140+ # A private branch with a private owner is rendered.
141+ private_owner = self.factory.makeTeam(
142+ displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
143+ branch = self.factory.makeAnyBranch(owner=private_owner)
144+ # Ensure the branch owner is rendered.
145+ url = canonical_url(branch, rootsite='code')
146+ user = self.factory.makePerson()
147+ # Subscribe the user so they can see the branch.
148+ with person_logged_in(private_owner):
149+ self.factory.makeBranchSubscription(branch, user, private_owner)
150+ browser = self._getBrowser(user)
151+ browser.open(url)
152+ soup = BeautifulSoup(browser.contents)
153+ self.assertIsNotNone(soup.find('a', text="PrivateTeam"))
154+
155+ def test_anonymous_view_branch_with_private_owner(self):
156+ # A branch with a private owner is not rendered for anon users.
157+ private_owner = self.factory.makeTeam(
158+ visibility=PersonVisibility.PRIVATE)
159+ branch = self.factory.makeAnyBranch(owner=private_owner)
160+ # Viewing the branch results in an error.
161+ url = canonical_url(branch, rootsite='code')
162+ browser = self._getBrowser()
163+ self.assertRaises(NotFound, browser.open, url)
164+
165+ def test_view_branch_with_private_subscriber(self):
166+ # A branch with a private subscriber is rendered.
167+ private_subscriber = self.factory.makeTeam(
168+ name="privateteam", visibility=PersonVisibility.PRIVATE)
169+ branch = self.factory.makeAnyBranch()
170+ with person_logged_in(branch.owner):
171+ self.factory.makeBranchSubscription(
172+ branch, private_subscriber, branch.owner)
173+ # Ensure the branch subscriber is rendered.
174+ url = canonical_url(branch, rootsite='code')
175+ user = self.factory.makePerson()
176+ browser = self._getBrowser(user)
177+ browser.open(url)
178+ soup = BeautifulSoup(browser.contents)
179+ self.assertIsNotNone(
180+ soup.find('div', attrs={'id': 'subscriber-privateteam'}))
181+
182+ def test_anonymous_view_branch_with_private_subscriber(self):
183+ # A branch with a private subscriber is not rendered for anon users.
184+ private_subscriber = self.factory.makeTeam(
185+ name="privateteam", visibility=PersonVisibility.PRIVATE)
186+ branch = self.factory.makeAnyBranch()
187+ with person_logged_in(branch.owner):
188+ self.factory.makeBranchSubscription(
189+ branch, private_subscriber, branch.owner)
190+ # Viewing the branch results in an error.
191+ url = canonical_url(branch, rootsite='code')
192+ browser = self._getBrowser()
193+ browser.open(url)
194+ soup = BeautifulSoup(browser.contents)
195+ self.assertIsNone(
196+ soup.find('div', attrs={'id': 'subscriber-privateteam'}))
197+
198+
199 class TestBranchAddView(TestCaseWithFactory):
200 """Test the BranchAddView view."""
201