← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/view-private-branch-artifacts-605130 into lp:launchpad with lp:~wallyworld/launchpad/private-owned-entity-traversal as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #605130 in Launchpad itself: "subscribed team cannot view  branch owned by (a different) private team"
  https://bugs.launchpad.net/launchpad/+bug/605130

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/view-private-branch-artifacts-605130/+merge/84060
-- 
https://code.launchpad.net/~wallyworld/launchpad/view-private-branch-artifacts-605130/+merge/84060
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/view-private-branch-artifacts-605130 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugbranch.py'
--- lib/lp/bugs/browser/bugbranch.py	2011-09-13 05:23:16 +0000
+++ lib/lp/bugs/browser/bugbranch.py	2011-12-01 03:03:10 +0000
@@ -2,6 +2,7 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Browser view classes for BugBranch-related objects."""
+from canonical.launchpad.webapp.authorization import precache_permission_for_objects
 
 __metaclass__ = type
 __all__ = [
@@ -110,7 +111,12 @@
     def merge_proposals(self):
         """Return a list of active proposals for the branch."""
         branch = self.context.branch
-        return latest_proposals_for_each_branch(branch.landing_targets)
+        proposals = latest_proposals_for_each_branch(branch.landing_targets)
+        if self.user is not None:
+            reviewers = [proposal.reviewer for proposal in proposals]
+            precache_permission_for_objects(
+                self.request, "launchpad.LimitedView", reviewers)
+        return proposals
 
     @property
     def show_branch_status(self):

=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2011-09-28 11:30:52 +0000
+++ lib/lp/code/browser/branch.py	2011-12-01 03:03:10 +0000
@@ -2,6 +2,7 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Branch views."""
+from zope.security.proxy import removeSecurityProxy
 
 __metaclass__ = type
 
@@ -96,7 +97,7 @@
     stepthrough,
     stepto,
     )
-from canonical.launchpad.webapp.authorization import check_permission
+from canonical.launchpad.webapp.authorization import check_permission, precache_permission_for_objects
 from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
 from canonical.launchpad.webapp.menu import structured
 from lp.app.browser.launchpad import Hierarchy
@@ -442,6 +443,11 @@
     def initialize(self):
         self.branch = self.context
         self.notices = []
+        authorised_people = [self.branch.owner]
+        if self.user is not None:
+            precache_permission_for_objects(
+                self.request, "launchpad.LimitedView", authorised_people)
+
         # Replace our context with a decorated branch, if it is not already
         # decorated.
         if not isinstance(self.context, DecoratedBranch):
@@ -532,7 +538,15 @@
     @cachedproperty
     def landing_targets(self):
         """Return a filtered list of landing targets."""
-        return latest_proposals_for_each_branch(self.context.landing_targets)
+        proposals = latest_proposals_for_each_branch(
+            self.context.landing_targets)
+        if self.user is not None:
+            reviewers = [
+            proposal.reviewer for proposal in proposals
+            if proposal.reviewer]
+            precache_permission_for_objects(
+                self.request, "launchpad.LimitedView", reviewers)
+        return proposals
 
     @property
     def latest_landing_candidates(self):

=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
--- lib/lp/code/browser/branchmergeproposal.py	2011-11-20 05:56:28 +0000
+++ lib/lp/code/browser/branchmergeproposal.py	2011-12-01 03:03:10 +0000
@@ -76,7 +76,7 @@
     stepthrough,
     stepto,
     )
-from canonical.launchpad.webapp.authorization import check_permission
+from canonical.launchpad.webapp.authorization import check_permission, precache_permission_for_objects
 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
 from canonical.launchpad.webapp.interfaces import IPrimaryContext
 from canonical.launchpad.webapp.menu import (
@@ -840,6 +840,12 @@
     def reviews(self):
         """Return the decorated votes for the proposal."""
         users_vote = self.context.getUsersVoteReference(self.user)
+        if self.user is not None:
+            reviewers = [
+                vote.reviewer for vote in self.context.votes
+                if vote.reviewer]
+            precache_permission_for_objects(
+                self.request, "launchpad.LimitedView", reviewers)
         return [DecoratedCodeReviewVoteReference(vote, self.user, users_vote)
                 for vote in self.context.votes]
 

=== modified file 'lib/lp/code/browser/branchsubscription.py'
--- lib/lp/code/browser/branchsubscription.py	2011-09-19 06:57:55 +0000
+++ lib/lp/code/browser/branchsubscription.py	2011-12-01 03:03:10 +0000
@@ -19,7 +19,7 @@
     canonical_url,
     LaunchpadView,
     )
-from canonical.launchpad.webapp.authorization import check_permission
+from canonical.launchpad.webapp.authorization import check_permission, precache_permission_for_objects
 from canonical.launchpad.webapp.interfaces import IPrimaryContext
 from canonical.launchpad.webapp.menu import structured
 from lp.app.browser.launchpadform import (
@@ -48,9 +48,16 @@
 
     def subscriptions(self):
         """Return a decorated list of branch subscriptions."""
+        if self.user is not None:
+            subscribers = [
+                subscription.person
+                for subscription in self.context.subscriptions]
+            precache_permission_for_objects(
+                self.request, "launchpad.LimitedView", subscribers)
+
         visible_subscriptions = [
             subscription for subscription in self.context.subscriptions
-            if check_permission('launchpad.View', subscription.person)]
+            if check_permission('launchpad.LimitedView', subscription.person)]
         return sorted(
             visible_subscriptions,
             key=lambda subscription: subscription.person.displayname)

=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py	2011-10-21 02:47:28 +0000
+++ lib/lp/code/browser/tests/test_branch.py	2011-12-01 03:03:10 +0000
@@ -5,12 +5,14 @@
 
 __metaclass__ = type
 
+from BeautifulSoup import BeautifulSoup
 from datetime import (
     datetime,
     )
 from textwrap import dedent
 
 import pytz
+from zope.publisher.interfaces import NotFound
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
@@ -26,7 +28,7 @@
     extract_text,
     find_tag_by_id,
     setupBrowser,
-    )
+    setupBrowserForUser)
 from lp.app.interfaces.headings import IRootContext
 from lp.bugs.interfaces.bugtask import (
     BugTaskStatus,
@@ -50,6 +52,7 @@
     BranchVisibilityRule,
     )
 from lp.code.interfaces.branchtarget import IBranchTarget
+from lp.registry.interfaces.person import PersonVisibility
 from lp.testing import (
     BrowserTestCase,
     login,
@@ -543,6 +546,138 @@
         self.assertEqual(linked_bug_urls[1], links[4]['href'])
 
 
+class TestBranchViewPrivateArtifacts(BrowserTestCase):
+    """ Tests that branches with private team artifacts can be viewed.
+
+    A Branch may be associated with a private team as follows:
+    - the owner
+    - a subscriber
+    - committed a revision
+    - be a code reviewer
+
+    A logged in user who is not authorised to see the private team(s) still
+    needs to be able to view the branch. The private team will be rendered in
+    the normal way, displaying the team name and Launchpad URL.
+    """
+
+    layer = DatabaseFunctionalLayer
+
+    def _getBrowser(self, user=None):
+        if user is None:
+            browser = setupBrowser()
+            logout()
+            return browser
+        else:
+            login_person(user)
+            return setupBrowserForUser(user=user)
+
+    def test_aaaa(self):
+        # A branch with a private owner is not rendered for anon users.
+        private_owner = self.factory.makeTeam(
+            visibility=PersonVisibility.PRIVATE)
+        # Viewing the branch results in an error.
+        url = canonical_url(removeSecurityProxy(private_owner))
+        user = self.factory.makePerson()
+        browser = self._getBrowser(user)
+        self.assertRaises(NotFound, browser.open, url)
+
+    def test_bbbb(self):
+        # A branch with a private owner is not rendered for anon users.
+        private_owner = self.factory.makeTeam(
+            visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch(owner=private_owner)
+        # Viewing the branch results in an error.
+        url = canonical_url(branch, rootsite='code')
+        browser = self._getBrowser()
+        self.assertRaises(NotFound, browser.open, url)
+
+    def test_view_branch_with_private_owner(self):
+        # A branch with a private owner is rendered.
+        private_owner = self.factory.makeTeam(
+            displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch(owner=private_owner)
+        # Ensure the branch owner is rendered.
+        url = canonical_url(branch, rootsite='code')
+        user = self.factory.makePerson()
+        browser = self._getBrowser(user)
+        browser.open(url)
+        soup = BeautifulSoup(browser.contents)
+        self.assertIsNotNone(soup.find('a', text="PrivateTeam"))
+
+    def test_anonymous_view_branch_with_private_owner(self):
+        # A branch with a private owner is not rendered for anon users.
+        private_owner = self.factory.makeTeam(
+            visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch(owner=private_owner)
+        # Viewing the branch results in an error.
+        url = canonical_url(branch, rootsite='code')
+        browser = self._getBrowser()
+        self.assertRaises(NotFound, browser.open, url)
+
+    def test_view_branch_with_private_subscriber(self):
+        # A branch with a private subscriber is rendered.
+        private_subscriber = self.factory.makeTeam(
+            name="privateteam", visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch()
+        with person_logged_in(branch.owner):
+            self.factory.makeBranchSubscription(
+                branch, private_subscriber, branch.owner)
+        # Ensure the branch subscriber is rendered.
+        url = canonical_url(branch, rootsite='code')
+        user = self.factory.makePerson()
+        browser = self._getBrowser(user)
+        browser.open(url)
+        soup = BeautifulSoup(browser.contents)
+        self.assertIsNotNone(
+            soup.find('div', attrs={'id': 'subscriber-privateteam'}))
+
+    def test_anonymous_view_branch_with_private_subscriber(self):
+        # A branch with a private subscriber is not rendered for anon users.
+        private_subscriber = self.factory.makeTeam(
+            name="privateteam", visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch()
+        with person_logged_in(branch.owner):
+            self.factory.makeBranchSubscription(
+                branch, private_subscriber, branch.owner)
+        # Viewing the branch results in an error.
+        url = canonical_url(branch, rootsite='code')
+        browser = self._getBrowser()
+        browser.open(url)
+        soup = BeautifulSoup(browser.contents)
+        self.assertIsNone(
+            soup.find('div', attrs={'id': 'subscriber-privateteam'}))
+
+    def test_view_branch_with_private_reviewer(self):
+        # A branch with a private reviewer is rendered.
+        private_reviewer = self.factory.makeTeam(
+            displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch()
+        with person_logged_in(branch.owner):
+            self.factory.makeBranchMergeProposal(
+                source_branch=branch, reviewer=private_reviewer)
+        # Ensure the branch reviewer is rendered.
+        url = canonical_url(branch, rootsite='code')
+        user = self.factory.makePerson()
+        browser = self._getBrowser(user)
+        browser.open(url)
+        soup = BeautifulSoup(browser.contents)
+        self.assertIsNotNone(
+            soup.find('a', text="PrivateTeam"))
+
+    def test_anonymous_view_branch_with_private_reviewer(self):
+        # A branch with a private reviewer is not rendered for anon users.
+        private_reviewer = self.factory.makeTeam(
+            visibility=PersonVisibility.PRIVATE)
+        branch = self.factory.makeAnyBranch()
+        with person_logged_in(branch.owner):
+            self.factory.makeBranchMergeProposal(
+                source_branch=branch, reviewer=private_reviewer)
+        # Viewing the branch results in an error.
+        url = canonical_url(branch, rootsite='code')
+        browser = self._getBrowser()
+        self.assertRaises(NotFound, browser.open, url)
+
+
 class TestBranchAddView(TestCaseWithFactory):
     """Test the BranchAddView view."""
 


Follow ups